mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 01:38:13 +00:00
Compare commits
1 Commits
adv-draft-
...
precalc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f3a9cd22e |
4
.github/workflows/maven-publish.yml
vendored
4
.github/workflows/maven-publish.yml
vendored
@@ -129,9 +129,7 @@ jobs:
|
|||||||
makeLatest: true
|
makeLatest: true
|
||||||
|
|
||||||
- name: 🔧 Install XML tools
|
- name: 🔧 Install XML tools
|
||||||
run: |
|
run: sudo apt-get install -y libxml2-utils
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libxml2-utils
|
|
||||||
|
|
||||||
- name: 🔼 Bump versionCode in root POM
|
- name: 🔼 Bump versionCode in root POM
|
||||||
id: bump_version
|
id: bump_version
|
||||||
|
|||||||
2
.github/workflows/test-build.yaml
vendored
2
.github/workflows/test-build.yaml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: ['17', '21']
|
java: [ '17' ]
|
||||||
name: Test with Java ${{ matrix.Java }}
|
name: Test with Java ${{ matrix.Java }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -66,9 +66,6 @@ forge-gui-mobile-dev/testAssets
|
|||||||
|
|
||||||
forge-gui/res/cardsfolder/*.bat
|
forge-gui/res/cardsfolder/*.bat
|
||||||
|
|
||||||
# Generated changelog file
|
|
||||||
forge-gui/release-files/CHANGES.txt
|
|
||||||
|
|
||||||
forge-gui/res/PerSetTrackingResults
|
forge-gui/res/PerSetTrackingResults
|
||||||
forge-gui/res/decks
|
forge-gui/res/decks
|
||||||
forge-gui/res/layouts
|
forge-gui/res/layouts
|
||||||
|
|||||||
33
.gitlab/issue_templates/Bug.md
Normal file
33
.gitlab/issue_templates/Bug.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
Summary
|
||||||
|
|
||||||
|
(Summarize the bug encountered concisely)
|
||||||
|
|
||||||
|
|
||||||
|
Steps to reproduce
|
||||||
|
|
||||||
|
(How one can reproduce the issue - this is very important. Specific cards and specific actions especially)
|
||||||
|
|
||||||
|
|
||||||
|
Which version of Forge are you on (Release, Snapshot? Desktop, Android?)
|
||||||
|
|
||||||
|
|
||||||
|
What is the current bug behavior?
|
||||||
|
|
||||||
|
(What actually happens)
|
||||||
|
|
||||||
|
|
||||||
|
What is the expected correct behavior?
|
||||||
|
|
||||||
|
(What you should see instead)
|
||||||
|
|
||||||
|
|
||||||
|
Relevant logs and/or screenshots
|
||||||
|
|
||||||
|
(Paste/Attach your game.log from the crash - please use code blocks (```)) Also, provide screenshots of the current state.
|
||||||
|
|
||||||
|
|
||||||
|
Possible fixes
|
||||||
|
|
||||||
|
(If you can, link to the line of code that might be responsible for the problem)
|
||||||
|
|
||||||
|
/label ~needs-investigation
|
||||||
15
.gitlab/issue_templates/Feature.md
Normal file
15
.gitlab/issue_templates/Feature.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Summary
|
||||||
|
|
||||||
|
(Summarize the feature you wish concisely)
|
||||||
|
|
||||||
|
|
||||||
|
Example screenshots
|
||||||
|
|
||||||
|
(If this is a UI change, please provide an example screenshot of how this feature might work)
|
||||||
|
|
||||||
|
|
||||||
|
Feature type
|
||||||
|
|
||||||
|
(Where in Forge does this belong? e.g. Quest Mode, Deck Editor, Limited, Constructed, etc.)
|
||||||
|
|
||||||
|
/label ~feature request
|
||||||
@@ -15,7 +15,7 @@ public class Main {
|
|||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
GuiBase.setInterface(new GuiMobile(Files.exists(Paths.get("./res"))?"./":"../forge-gui/"));
|
GuiBase.setInterface(new GuiMobile(Files.exists(Paths.get("./res"))?"./":"../forge-gui/"));
|
||||||
GuiBase.setDeviceInfo(null, 0, 0, System.getProperty("user.home") + "/Downloads/");
|
GuiBase.setDeviceInfo(null, 0, 0);
|
||||||
new EditorMainWindow(Config.instance());
|
new EditorMainWindow(Config.instance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ public class AiController {
|
|||||||
private int lastAttackAggression;
|
private int lastAttackAggression;
|
||||||
private boolean useLivingEnd;
|
private boolean useLivingEnd;
|
||||||
private List<SpellAbility> skipped;
|
private List<SpellAbility> skipped;
|
||||||
private boolean timeoutReached;
|
|
||||||
|
|
||||||
public AiController(final Player computerPlayer, final Game game0) {
|
public AiController(final Player computerPlayer, final Game game0) {
|
||||||
player = computerPlayer;
|
player = computerPlayer;
|
||||||
@@ -887,8 +886,27 @@ public class AiController {
|
|||||||
private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) {
|
private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
|
|
||||||
if (sa.hasParam("AICheckSVar") && !aiShouldRun(sa, sa, host, null)) {
|
// Check a predefined condition
|
||||||
return AiPlayDecision.AnotherTime;
|
if (sa.hasParam("AICheckSVar")) {
|
||||||
|
final String svarToCheck = sa.getParam("AICheckSVar");
|
||||||
|
String comparator = "GE";
|
||||||
|
int compareTo = 1;
|
||||||
|
|
||||||
|
if (sa.hasParam("AISVarCompare")) {
|
||||||
|
final String fullCmp = sa.getParam("AISVarCompare");
|
||||||
|
comparator = fullCmp.substring(0, 2);
|
||||||
|
final String strCmpTo = fullCmp.substring(2);
|
||||||
|
try {
|
||||||
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
|
} catch (final Exception ignored) {
|
||||||
|
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
|
||||||
|
if (!Expressions.compare(left, comparator, compareTo)) {
|
||||||
|
return AiPlayDecision.AnotherTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is the "heaviest" check, which also sets up targets, defines X, etc.
|
// this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||||
@@ -906,7 +924,7 @@ public class AiController {
|
|||||||
|
|
||||||
// check if enough left (pass memory indirectly because we don't want to include those)
|
// check if enough left (pass memory indirectly because we don't want to include those)
|
||||||
Set<Card> tappedForMana = AiCardMemory.getMemorySet(player, MemorySet.PAYS_TAP_COST);
|
Set<Card> tappedForMana = AiCardMemory.getMemorySet(player, MemorySet.PAYS_TAP_COST);
|
||||||
if (tappedForMana != null && !tappedForMana.isEmpty() &&
|
if (tappedForMana != null && tappedForMana.isEmpty() &&
|
||||||
!ComputerUtilCost.checkTapTypeCost(player, sa.getPayCosts(), host, sa, new CardCollection(tappedForMana))) {
|
!ComputerUtilCost.checkTapTypeCost(player, sa.getPayCosts(), host, sa, new CardCollection(tappedForMana))) {
|
||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
}
|
}
|
||||||
@@ -1646,9 +1664,6 @@ public class AiController {
|
|||||||
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case of infinite loop reset below would not be reached
|
|
||||||
timeoutReached = false;
|
|
||||||
|
|
||||||
FutureTask<SpellAbility> future = new FutureTask<>(() -> {
|
FutureTask<SpellAbility> future = new FutureTask<>(() -> {
|
||||||
//avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast...
|
//avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast...
|
||||||
boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0);
|
boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0);
|
||||||
@@ -1658,11 +1673,6 @@ public class AiController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeoutReached) {
|
|
||||||
timeoutReached = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.getHostCard().hasKeyword(Keyword.STORM)
|
if (sa.getHostCard().hasKeyword(Keyword.STORM)
|
||||||
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
||||||
&& player.getZone(ZoneType.Hand).contains(
|
&& player.getZone(ZoneType.Hand).contains(
|
||||||
@@ -1742,10 +1752,7 @@ public class AiController {
|
|||||||
t.stop();
|
t.stop();
|
||||||
} catch (UnsupportedOperationException ex) {
|
} catch (UnsupportedOperationException ex) {
|
||||||
// Android and Java 20 dropped support to stop so sadly thread will keep running
|
// Android and Java 20 dropped support to stop so sadly thread will keep running
|
||||||
timeoutReached = true;
|
|
||||||
future.cancel(true);
|
future.cancel(true);
|
||||||
// TODO wait a few more seconds to try and exit at a safe point before letting the engine continue
|
|
||||||
// TODO mark some as skipped to increase chance to find something playable next priority
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1798,9 +1805,14 @@ public class AiController {
|
|||||||
* @param sa the sa
|
* @param sa the sa
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public final boolean aiShouldRun(final CardTraitBase effect, final SpellAbility sa, final Card host, final GameEntity affected) {
|
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) {
|
||||||
|
Card hostCard = effect.getHostCard();
|
||||||
|
if (hostCard.hasAlternateState()) {
|
||||||
|
hostCard = game.getCardState(hostCard);
|
||||||
|
}
|
||||||
|
|
||||||
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
|
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
|
||||||
final Player controller = host.getController();
|
final Player controller = hostCard.getController();
|
||||||
if (affected instanceof Player) {
|
if (affected instanceof Player) {
|
||||||
return !((Player) affected).isOpponentOf(controller);
|
return !((Player) affected).isOpponentOf(controller);
|
||||||
}
|
}
|
||||||
@@ -1809,6 +1821,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (effect.hasParam("AICheckSVar")) {
|
if (effect.hasParam("AICheckSVar")) {
|
||||||
|
System.out.println("aiShouldRun?" + sa);
|
||||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
final String svarToCheck = effect.getParam("AICheckSVar");
|
||||||
String comparator = "GE";
|
String comparator = "GE";
|
||||||
int compareTo = 1;
|
int compareTo = 1;
|
||||||
@@ -1821,9 +1834,9 @@ public class AiController {
|
|||||||
compareTo = Integer.parseInt(strCmpTo);
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), effect);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
|
||||||
} else {
|
} else {
|
||||||
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1831,12 +1844,13 @@ public class AiController {
|
|||||||
int left = 0;
|
int left = 0;
|
||||||
|
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
left = AbilityUtils.calculateAmount(host, svarToCheck, effect);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
|
||||||
} else {
|
} else {
|
||||||
left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||||
}
|
}
|
||||||
|
System.out.println("aiShouldRun?" + left + comparator + compareTo);
|
||||||
return Expressions.compare(left, comparator, compareTo);
|
return Expressions.compare(left, comparator, compareTo);
|
||||||
} else if (effect.isKeyword(Keyword.DREDGE)) {
|
} else if (effect.hasParam("AICheckDredge")) {
|
||||||
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
||||||
} else return sa != null && doTrigger(sa, false);
|
} else return sa != null && doTrigger(sa, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,15 +29,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
private final CardCollection tapped;
|
private final CardCollection tapped;
|
||||||
|
|
||||||
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect) {
|
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect) {
|
||||||
this(ai0, sa, effect, false);
|
|
||||||
}
|
|
||||||
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect, final boolean payMana) {
|
|
||||||
super(ai0, effect, sa, sa.getHostCard());
|
super(ai0, effect, sa, sa.getHostCard());
|
||||||
|
|
||||||
discarded = new CardCollection();
|
discarded = new CardCollection();
|
||||||
tapped = new CardCollection();
|
tapped = new CardCollection();
|
||||||
Set<Card> tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST);
|
Set<Card> tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST);
|
||||||
if (!payMana && tappedForMana != null) {
|
if (tappedForMana != null) {
|
||||||
tapped.addAll(tappedForMana);
|
tapped.addAll(tappedForMana);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +110,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
||||||
}
|
}
|
||||||
return PaymentDecision.card(randomSubset);
|
return PaymentDecision.card(randomSubset);
|
||||||
} else if (type.contains("+WithDifferentNames")) {
|
} else if (type.equals("DifferentNames")) {
|
||||||
CardCollection differentNames = new CardCollection();
|
CardCollection differentNames = new CardCollection();
|
||||||
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
||||||
while (c > 0) {
|
while (c > 0) {
|
||||||
@@ -566,7 +563,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
removed += thisRemove;
|
removed += thisRemove;
|
||||||
table.put(null, prefCard, cType, thisRemove);
|
table.put(null, prefCard, CounterType.get(cType), thisRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,7 +573,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||||
final int c = cost.getAbilityAmount(ability);
|
final int c = cost.getAbilityAmount(ability);
|
||||||
final Card originalHost = ObjectUtils.getIfNull(ability.getOriginalHost(), source);
|
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
|
||||||
|
|
||||||
if (c <= 0) {
|
if (c <= 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -719,7 +716,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
toRemove += over;
|
||||||
table.put(null, crd, CounterEnumType.QUEST, over);
|
table.put(null, crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -767,7 +767,7 @@ public class ComputerUtil {
|
|||||||
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
|
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
|
||||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
||||||
|
|
||||||
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterEnumType.STUN));
|
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
|
||||||
|
|
||||||
if (untap) {
|
if (untap) {
|
||||||
typeList.remove(activate);
|
typeList.remove(activate);
|
||||||
@@ -2542,7 +2542,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
boolean opponent = controller.isOpponentOf(ai);
|
boolean opponent = controller.isOpponentOf(ai);
|
||||||
|
|
||||||
final CounterType p1p1Type = CounterEnumType.P1P1;
|
final CounterType p1p1Type = CounterType.get(CounterEnumType.P1P1);
|
||||||
|
|
||||||
if (!sa.hasParam("AILogic")) {
|
if (!sa.hasParam("AILogic")) {
|
||||||
return Aggregates.random(options);
|
return Aggregates.random(options);
|
||||||
@@ -3104,38 +3104,41 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) {
|
public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
if (source == null || !sa.hasParam("AITgts")) {
|
if (source == null) { return srcList; }
|
||||||
return srcList;
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollection list;
|
if (sa.hasParam("AITgts")) {
|
||||||
String aiTgts = sa.getParam("AITgts");
|
CardCollection list;
|
||||||
if (aiTgts.startsWith("BetterThan")) {
|
String aiTgts = sa.getParam("AITgts");
|
||||||
int value = 0;
|
if (aiTgts.startsWith("BetterThan")) {
|
||||||
if (aiTgts.endsWith("Source")) {
|
int value = 0;
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
if (aiTgts.endsWith("Source")) {
|
||||||
if (source.isEnchanted()) {
|
value = ComputerUtilCard.evaluateCreature(source);
|
||||||
for (Card enc : source.getEnchantedBy()) {
|
if (source.isEnchanted()) {
|
||||||
if (enc.getController().equals(ai)) {
|
for (Card enc : source.getEnchantedBy()) {
|
||||||
value += 100; // is 100 per AI's own aura enough?
|
if (enc.getController().equals(ai)) {
|
||||||
|
value += 100; // is 100 per AI's own aura enough?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (aiTgts.contains("EvalRating.")) {
|
||||||
|
value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa);
|
||||||
|
} else {
|
||||||
|
System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa);
|
||||||
|
value = ComputerUtilCard.evaluateCreature(source);
|
||||||
}
|
}
|
||||||
} else if (aiTgts.contains("EvalRating.")) {
|
final int totalValue = value;
|
||||||
value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa);
|
list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30);
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa);
|
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa);
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
}
|
||||||
|
|
||||||
|
if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) {
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return srcList;
|
||||||
}
|
}
|
||||||
final int totalValue = value;
|
|
||||||
list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30);
|
|
||||||
} else {
|
|
||||||
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
return srcList;
|
return srcList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -974,13 +974,17 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pBonus = 0;
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumAtt")) {
|
if (!ability.hasParam("NumAtt")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||||
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||||
|
if (pBonus > 0) {
|
||||||
|
power += pBonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -994,11 +998,12 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||||
}
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||||
|
if (pBonus > 0) {
|
||||||
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
power += pBonus;
|
||||||
power += pBonus;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,13 +1107,17 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int tBonus = 0;
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumDef")) {
|
if (!ability.hasParam("NumDef")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||||
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||||
|
if (tBonus > 0) {
|
||||||
|
toughness += tBonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1122,11 +1131,12 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||||
}
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||||
|
if (tBonus > 0) {
|
||||||
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
toughness += tBonus;
|
||||||
toughness += tBonus;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toughness;
|
return toughness;
|
||||||
@@ -1295,7 +1305,6 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pBonus = 0;
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumAtt")) {
|
if (!ability.hasParam("NumAtt")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1305,8 +1314,11 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost()) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||||
|
if (pBonus > 0) {
|
||||||
|
power += pBonus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
@@ -1321,14 +1333,13 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost()) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||||
|
if (pBonus > 0) {
|
||||||
|
power += pBonus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
|
||||||
power += pBonus;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
@@ -1519,14 +1530,16 @@ public class ComputerUtilCombat {
|
|||||||
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
|
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int tBonus = 0;
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumDef")) {
|
if (!ability.hasParam("NumDef")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
toughness += AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1540,11 +1553,10 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||||
}
|
if (tBonus > 0) {
|
||||||
|
toughness += tBonus;
|
||||||
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
}
|
||||||
toughness += tBonus;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toughness;
|
return toughness;
|
||||||
|
|||||||
@@ -287,9 +287,7 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int amount = ma.hasParam("Amount") ? AbilityUtils.calculateAmount(ma.getHostCard(), ma.getParam("Amount"), ma) : 1;
|
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) {
|
||||||
if (amount <= 0) {
|
|
||||||
// wrong gamestate for variable amount
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,14 +351,9 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// these should come last since they reserve the paying cards
|
|
||||||
// (this means if a mana ability has both parts it doesn't currently undo reservations if the second part fails)
|
|
||||||
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
|
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return paymentChoice;
|
return paymentChoice;
|
||||||
}
|
}
|
||||||
@@ -450,6 +443,7 @@ public class ComputerUtilMana {
|
|||||||
manaProduced = manaProduced.replace(s, color);
|
manaProduced = manaProduced.replace(s, color);
|
||||||
}
|
}
|
||||||
} else if (saMana.hasParam("ReplaceColor")) {
|
} else if (saMana.hasParam("ReplaceColor")) {
|
||||||
|
// replace color
|
||||||
String color = saMana.getParam("ReplaceColor");
|
String color = saMana.getParam("ReplaceColor");
|
||||||
if ("Chosen".equals(color)) {
|
if ("Chosen".equals(color)) {
|
||||||
if (card.hasChosenColor()) {
|
if (card.hasChosenColor()) {
|
||||||
@@ -741,8 +735,7 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
||||||
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
|
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
|
||||||
// not a good idea to sac a card that you're targeting with the SA you're paying for
|
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||||
saExcludeList.add(saPayment);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -816,11 +809,11 @@ public class ComputerUtilMana {
|
|||||||
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove to prevent re-usage since resources don't get consumed
|
// remove from available lists
|
||||||
sourcesForShards.values().removeIf(CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
sourcesForShards.values().removeIf(CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
||||||
} else {
|
} else {
|
||||||
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||||
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect, true))) {
|
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect))) {
|
||||||
saList.remove(saPayment);
|
saList.remove(saPayment);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -829,10 +822,8 @@ public class ComputerUtilMana {
|
|||||||
// subtract mana from mana pool
|
// subtract mana from mana pool
|
||||||
manapool.payManaFromAbility(sa, cost, saPayment);
|
manapool.payManaFromAbility(sa, cost, saPayment);
|
||||||
|
|
||||||
// need to consider if another use is now prevented
|
// no need to remove abilities from resource map,
|
||||||
if (!cost.isPaid() && saPayment.isActivatedAbility() && !saPayment.getRestrictions().canPlay(saPayment.getHostCard(), saPayment)) {
|
// once their costs are paid and consume resources, they can not be used again
|
||||||
sourcesForShards.values().removeIf(s -> s == saPayment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasConverge) {
|
if (hasConverge) {
|
||||||
// hack to prevent converge re-using sources
|
// hack to prevent converge re-using sources
|
||||||
@@ -1505,7 +1496,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cost.isReusuableResource()) {
|
if (!cost.isReusuableResource()) {
|
||||||
for (CostPart part : cost.getCostParts()) {
|
for(CostPart part : cost.getCostParts()) {
|
||||||
if (part instanceof CostSacrifice && !part.payCostFromSource()) {
|
if (part instanceof CostSacrifice && !part.payCostFromSource()) {
|
||||||
unpreferredCost = true;
|
unpreferredCost = true;
|
||||||
}
|
}
|
||||||
@@ -1596,8 +1587,10 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
// don't use abilities with dangerous drawbacks
|
// don't use abilities with dangerous drawbacks
|
||||||
AbilitySub sub = m.getSubAbility();
|
AbilitySub sub = m.getSubAbility();
|
||||||
if (sub != null && !SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
if (sub != null) {
|
||||||
continue;
|
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
||||||
@@ -1665,6 +1658,7 @@ public class ComputerUtilMana {
|
|||||||
if (replaced.contains("C")) {
|
if (replaced.contains("C")) {
|
||||||
manaMap.put(ManaAtom.COLORLESS, m);
|
manaMap.put(ManaAtom.COLORLESS, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,11 +460,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
||||||
Card host = replacementEffect.getHostCard();
|
return brains.aiShouldRun(replacementEffect, effectSA, affected);
|
||||||
if (host.hasAlternateState()) {
|
|
||||||
host = host.getGame().getCardState(host);
|
|
||||||
}
|
|
||||||
return brains.aiShouldRun(replacementEffect, effectSA, host, affected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1351,11 +1347,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
// Ai won't understand that anyway
|
// Ai won't understand that anyway
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
|
|
||||||
// Ai won't understand that anyway
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<DeckSection, List<? extends PaperCard>> complainCardsCantPlayWell(Deck myDeck) {
|
public Map<DeckSection, List<? extends PaperCard>> complainCardsCantPlayWell(Deck myDeck) {
|
||||||
// TODO check if profile detection set to Auto
|
// TODO check if profile detection set to Auto
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ public class SpecialAiLogic {
|
|||||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
||||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||||
|
|
||||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterEnumType.POISON)) {
|
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ public class SpecialAiLogic {
|
|||||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
||||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||||
|
|
||||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterEnumType.POISON)) {
|
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiLogic.equals("SurpriseBlock")) {
|
if (aiLogic.equals("BeforeCombat")) {
|
||||||
|
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (aiLogic.equals("SurpriseBlock")) {
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -294,7 +298,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
try {
|
||||||
|
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// This happens when Origin is something like
|
||||||
|
// "Graveyard,Library" (Doomsday)
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final String destination = sa.getParam("Destination");
|
final String destination = sa.getParam("Destination");
|
||||||
|
|
||||||
@@ -761,8 +771,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (aiLogic.equals("BeforeCombat")) {
|
|
||||||
return !ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
@@ -889,6 +897,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||||
|
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||||
|
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
|
||||||
|
list = CardLists.filter(list, card -> ComputerUtilCard.evaluateCreature(card) > ComputerUtilCard.evaluateCreature(source) + 30);
|
||||||
|
}
|
||||||
|
|
||||||
if (source.isInZone(ZoneType.Hand)) {
|
if (source.isInZone(ZoneType.Hand)) {
|
||||||
list = CardLists.filter(list, CardPredicates.nameNotEquals(source.getName())); // Don't get the same card back.
|
list = CardLists.filter(list, CardPredicates.nameNotEquals(source.getName())); // Don't get the same card back.
|
||||||
@@ -897,6 +908,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
if (sa.hasParam("AttachedTo")) {
|
if (sa.hasParam("AttachedTo")) {
|
||||||
list = CardLists.filter(list, c -> {
|
list = CardLists.filter(list, c -> {
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
@@ -1239,12 +1252,53 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if max CMC exceeded, do not choose this card (but keep looking for other options)
|
||||||
|
if (sa.hasParam("MaxTotalTargetCMC")) {
|
||||||
|
if (choice.getCMC() > sa.getTargetRestrictions().getMaxTotalCMC(choice, sa) - sa.getTargets().getTotalTargetedCMC()) {
|
||||||
|
list.remove(choice);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if max power exceeded, do not choose this card (but keep looking for other options)
|
||||||
|
if (sa.hasParam("MaxTotalTargetPower")) {
|
||||||
|
if (choice.getNetPower() > sa.getTargetRestrictions().getMaxTotalPower(choice, sa) -sa.getTargets().getTotalTargetedPower()) {
|
||||||
|
list.remove(choice);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// honor the Same Creature Type restriction
|
||||||
|
if (sa.getTargetRestrictions().isWithSameCreatureType()) {
|
||||||
|
Card firstTarget = sa.getTargetCard();
|
||||||
|
if (firstTarget != null && !choice.sharesCreatureTypeWith(firstTarget)) {
|
||||||
|
list.remove(choice);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
if (sa.canTarget(choice)) {
|
if (sa.canTarget(choice)) {
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Honor the Single Zone restriction. For now, simply remove targets that do not belong to the same zone as the first targeted card.
|
||||||
|
// TODO: ideally the AI should consider at this point which targets exactly to pick (e.g. one card in the first player's graveyard
|
||||||
|
// vs. two cards in the second player's graveyard, which cards are more relevant to be targeted, etc.). Consider improving.
|
||||||
|
if (sa.getTargetRestrictions().isSingleZone()) {
|
||||||
|
Card firstTgt = sa.getTargetCard();
|
||||||
|
CardCollection toRemove = new CardCollection();
|
||||||
|
if (firstTgt != null) {
|
||||||
|
for (Card t : sa.getTargets().getTargetCards()) {
|
||||||
|
if (!t.getController().equals(firstTgt.getController())) {
|
||||||
|
toRemove.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sa.getTargets().removeAll(toRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,10 +96,6 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
chance = cloneTgtAI(sa);
|
chance = cloneTgtAI(sa);
|
||||||
} else {
|
} else {
|
||||||
if (sa.isReplacementAbility() && host.isCloned()) {
|
|
||||||
// prevent StackOverflow from infinite loop copying another ETB RE
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
|
||||||
}
|
|
||||||
if (sa.hasParam("Choices")) {
|
if (sa.hasParam("Choices")) {
|
||||||
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
|
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
sa.getParam("Choices"), host.getController(), host, sa);
|
sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
@@ -192,7 +188,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
||||||
|
|
||||||
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||||
: "Permanent.YouDontCtrl+!named" + name + ",Permanent.nonLegendary+!named" + name;
|
: "Permanent.YouDontCtrl+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name;
|
||||||
|
|
||||||
// TODO: rewrite this block so that this is done somehow more elegantly
|
// TODO: rewrite this block so that this is done somehow more elegantly
|
||||||
if (canCloneLegendary) {
|
if (canCloneLegendary) {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(
|
return new AiAbilityDecision(
|
||||||
sa.isTargetNumberValid() ? 100 : 0,
|
sa.isTargetNumberValid() && !sa.getTargets().isEmpty() ? 100 : 0,
|
||||||
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,15 +53,17 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (mandatory) {
|
|
||||||
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
|
||||||
if (sa.isTargetNumberValid()) {
|
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
return decision;
|
|
||||||
} else {
|
} else {
|
||||||
return canPlay(aiPlayer, sa);
|
if (mandatory) {
|
||||||
|
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
||||||
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decision;
|
||||||
|
} else {
|
||||||
|
return canPlay(aiPlayer, sa);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
// Not at EOT phase
|
// Not at EOT phase
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
}
|
}
|
||||||
} else if ("DuplicatePerms".equals(aiLogic)) {
|
} if ("DuplicatePerms".equals(aiLogic)) {
|
||||||
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
if (valid.size() < 2) {
|
if (valid.size() < 2) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
@@ -212,7 +212,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public abstract class CountersAi extends SpellAbilityAi {
|
|||||||
} else if (type.equals("DIVINITY")) {
|
} else if (type.equals("DIVINITY")) {
|
||||||
final CardCollection boon = CardLists.filter(list, c -> c.getCounters(CounterEnumType.DIVINITY) == 0);
|
final CardCollection boon = CardLists.filter(list, c -> c.getCounters(CounterEnumType.DIVINITY) == 0);
|
||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon);
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon);
|
||||||
} else if (CounterType.getType(type).isKeywordCounter()) {
|
} else if (CounterType.get(type).isKeywordCounter()) {
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(CardLists.getNotKeyword(list, type));
|
choice = ComputerUtilCard.getBestCreatureAI(CardLists.getNotKeyword(list, type));
|
||||||
} else {
|
} else {
|
||||||
// The AI really should put counters on cards that can use it.
|
// The AI really should put counters on cards that can use it.
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (counterType == null || counterType.is(type)) {
|
if (counterType == null || counterType.is(type)) {
|
||||||
addTargetsByCounterType(ai, sa, aiList, type);
|
addTargetsByCounterType(ai, sa, aiList, CounterType.get(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
if (!oppList.isEmpty()) {
|
if (!oppList.isEmpty()) {
|
||||||
// not enough targets
|
// not enough targets
|
||||||
if (sa.canAddMoreTarget()) {
|
if (sa.canAddMoreTarget()) {
|
||||||
final CounterType type = CounterEnumType.M1M1;
|
final CounterType type = CounterType.get(CounterEnumType.M1M1);
|
||||||
if (counterType == null || counterType == type) {
|
if (counterType == null || counterType == type) {
|
||||||
addTargetsByCounterType(ai, sa, oppList, type);
|
addTargetsByCounterType(ai, sa, oppList, type);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
// Proliferate is always optional for all, no need to select best
|
// Proliferate is always optional for all, no need to select best
|
||||||
|
|
||||||
final CounterType poison = CounterEnumType.POISON;
|
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
||||||
|
|
||||||
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
||||||
// because countertype can't be chosen anymore, only look for poison counters
|
// because countertype can't be chosen anymore, only look for poison counters
|
||||||
|
|||||||
@@ -92,8 +92,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return chance > MyRandom.getRandom().nextFloat();
|
return chance > MyRandom.getRandom().nextFloat();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isKeyword(Keyword.LEVEL_UP)) {
|
if (sa.isKeyword(Keyword.LEVEL_UP)) {
|
||||||
@@ -123,6 +124,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
|
CardCollection list;
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.isDividedAsYouChoose();
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
@@ -168,7 +170,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||||
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
||||||
|
|
||||||
oppCreatM1 = CardLists.filter(oppCreatM1, input -> input.getNetToughness() <= 1 && input.canReceiveCounters(CounterEnumType.M1M1));
|
oppCreatM1 = CardLists.filter(oppCreatM1, input -> input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1)));
|
||||||
|
|
||||||
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
||||||
if (best != null) {
|
if (best != null) {
|
||||||
@@ -290,8 +292,10 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
if (willActivate) {
|
if (willActivate) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
} else if (logic.equals("ChargeToBestCMC")) {
|
} else if (logic.equals("ChargeToBestCMC")) {
|
||||||
return doChargeToCMCLogic(ai, sa);
|
return doChargeToCMCLogic(ai, sa);
|
||||||
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
||||||
@@ -332,7 +336,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
|
|
||||||
if (!source.canReceiveCounters(CounterEnumType.P1P1) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return doCombatAdaptLogic(source, amount, combat);
|
return doCombatAdaptLogic(source, amount, combat);
|
||||||
@@ -344,7 +348,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
return FightAi.canFight(ai, sa, nPump, nPump);
|
return FightAi.canFightAi(ai, sa, nPump, nPump);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amountStr.equals("X")) {
|
if (amountStr.equals("X")) {
|
||||||
@@ -438,16 +442,17 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
sa.addDividedAllocation(c, amount);
|
sa.addDividedAllocation(c, amount);
|
||||||
return decision;
|
return decision;
|
||||||
} else if (!hasSacCost) {
|
} else {
|
||||||
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
if (!hasSacCost) {
|
||||||
return decision;
|
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list;
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
@@ -603,21 +608,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int currCounters = cards.get(0).getCounters(CounterType.getType(type));
|
final int currCounters = cards.get(0).getCounters(CounterType.get(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
|
// each non +1/+1 counter on the card is a 10% chance of not
|
||||||
// activating this ability.
|
// activating this ability.
|
||||||
|
|
||||||
@@ -632,7 +623,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Useless since the card already has the keyword (or for another reason)
|
// Useless since the card already has the keyword (or for another reason)
|
||||||
if (ComputerUtil.isUselessCounter(CounterType.getType(type), cards.get(0))) {
|
if (ComputerUtil.isUselessCounter(CounterType.get(type), cards.get(0))) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -679,12 +670,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
CardCollection list;
|
CardCollection list = null;
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
}
|
}
|
||||||
|
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
|
||||||
if (list.isEmpty() && isMandatoryTrigger) {
|
if (list.isEmpty() && isMandatoryTrigger) {
|
||||||
@@ -700,8 +693,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|| sa.getTargets().isEmpty()) {
|
|| sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
@@ -743,7 +737,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String aiLogic = sa.getParam("AILogic");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
boolean preferred = true;
|
||||||
|
CardCollection list;
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.isDividedAsYouChoose();
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
@@ -762,10 +758,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("ChargeToBestCMC".equals(aiLogic)) {
|
if ("ChargeToBestCMC".equals(aiLogic)) {
|
||||||
if (mandatory) {
|
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
|
} else if (mandatory) {
|
||||||
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return doChargeToCMCLogic(ai, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
@@ -789,6 +789,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// things like Powder Keg, which are way too complex for the AI
|
// things like Powder Keg, which are way too complex for the AI
|
||||||
}
|
}
|
||||||
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
||||||
|
// can only target opponent
|
||||||
PlayerCollection playerList = new PlayerCollection(IterableUtil.filter(
|
PlayerCollection playerList = new PlayerCollection(IterableUtil.filter(
|
||||||
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
||||||
|
|
||||||
@@ -803,32 +804,34 @@ public class CountersPutAi extends CountersAi {
|
|||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ("Fight".equals(aiLogic) || "PowerDmg".equals(aiLogic)) {
|
String logic = sa.getParam("AILogic");
|
||||||
|
if ("Fight".equals(logic) || "PowerDmg".equals(logic)) {
|
||||||
int nPump = 0;
|
int nPump = 0;
|
||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
AiAbilityDecision decision = FightAi.canFight(ai, sa, nPump, nPump);
|
AiAbilityDecision decision = FightAi.canFightAi(ai, sa, nPump, nPump);
|
||||||
if (decision.willingToPlay()) {
|
if (decision.willingToPlay()) {
|
||||||
return decision;
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
|
||||||
|
|
||||||
Iterable<Card> filteredField;
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
filteredField = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
filteredField = ai.getCardsIn(ZoneType.Battlefield);
|
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
}
|
}
|
||||||
CardCollection list = CardLists.getTargetableCards(filteredField, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
|
||||||
int totalTargets = list.size();
|
|
||||||
boolean preferred = true;
|
|
||||||
|
|
||||||
|
// Filter AI-specific targets if provided
|
||||||
|
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||||
|
|
||||||
|
int totalTargets = list.size();
|
||||||
|
|
||||||
|
sa.resetTargets();
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
|
// When things are mandatory, gotta handle a little differently
|
||||||
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
@@ -853,7 +856,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice;
|
Card choice = null;
|
||||||
|
|
||||||
// Choose targets here:
|
// Choose targets here:
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
@@ -862,27 +865,33 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (choice == null && mandatory) {
|
if (choice == null && mandatory) {
|
||||||
choice = Aggregates.random(list);
|
choice = Aggregates.random(list);
|
||||||
}
|
}
|
||||||
} else if (type.equals("M1M1")) {
|
|
||||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
|
||||||
} else {
|
} else {
|
||||||
choice = Aggregates.random(list);
|
if (type.equals("M1M1")) {
|
||||||
|
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
|
} else {
|
||||||
|
choice = Aggregates.random(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (preferred) {
|
|
||||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
|
||||||
choice = chooseBoonTarget(list, type);
|
|
||||||
if (choice == null && mandatory) {
|
|
||||||
choice = Aggregates.random(list);
|
|
||||||
}
|
|
||||||
} else if (type.equals("P1P1")) {
|
|
||||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
|
||||||
} else {
|
} else {
|
||||||
choice = Aggregates.random(list);
|
if (preferred) {
|
||||||
|
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||||
|
choice = chooseBoonTarget(list, type);
|
||||||
|
if (choice == null && mandatory) {
|
||||||
|
choice = Aggregates.random(list);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (type.equals("P1P1")) {
|
||||||
|
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
|
} else {
|
||||||
|
choice = Aggregates.random(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (choice != null && divided) {
|
if (choice != null && divided) {
|
||||||
|
int alloc = Math.max(amount / totalTargets, 1);
|
||||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||||
sa.addDividedAllocation(choice, left);
|
sa.addDividedAllocation(choice, left);
|
||||||
} else {
|
} else {
|
||||||
int alloc = Math.max(amount / totalTargets, 1);
|
|
||||||
sa.addDividedAllocation(choice, alloc);
|
sa.addDividedAllocation(choice, alloc);
|
||||||
left -= alloc;
|
left -= alloc;
|
||||||
}
|
}
|
||||||
@@ -952,8 +961,8 @@ public class CountersPutAi extends CountersAi {
|
|||||||
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
// Bolster does use this
|
// Bolster does use this
|
||||||
// TODO need more or less logic there?
|
// TODO need more or less logic there?
|
||||||
final CounterType m1m1 = CounterEnumType.M1M1;
|
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||||
final CounterType p1p1 = CounterEnumType.P1P1;
|
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||||
|
|
||||||
// no logic if there is no options or no to choice
|
// no logic if there is no options or no to choice
|
||||||
if (!isOptional && Iterables.size(options) <= 1) {
|
if (!isOptional && Iterables.size(options) <= 1) {
|
||||||
@@ -972,7 +981,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
final boolean isCurse = sa.isCurse();
|
||||||
|
|
||||||
|
if (isCurse) {
|
||||||
final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents());
|
final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents());
|
||||||
|
|
||||||
if (!opponents.isEmpty()) {
|
if (!opponents.isEmpty()) {
|
||||||
@@ -1069,10 +1080,11 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Player ai = sa.getActivatingPlayer();
|
Player ai = sa.getActivatingPlayer();
|
||||||
GameEntity e = (GameEntity) params.get("Target");
|
GameEntity e = (GameEntity) params.get("Target");
|
||||||
// for Card try to select not useless counter
|
// for Card try to select not useless counter
|
||||||
if (e instanceof Card c) {
|
if (e instanceof Card) {
|
||||||
|
Card c = (Card) e;
|
||||||
if (c.getController().isOpponentOf(ai)) {
|
if (c.getController().isOpponentOf(ai)) {
|
||||||
if (options.contains(CounterEnumType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||||
return CounterEnumType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (ComputerUtil.isNegativeCounter(type, c)) {
|
if (ComputerUtil.isNegativeCounter(type, c)) {
|
||||||
@@ -1086,14 +1098,15 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (e instanceof Player p) {
|
} else if (e instanceof Player) {
|
||||||
|
Player p = (Player) e;
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
if (options.contains(CounterEnumType.POISON)) {
|
if (options.contains(CounterType.get(CounterEnumType.POISON))) {
|
||||||
return CounterEnumType.POISON;
|
return CounterType.get(CounterEnumType.POISON);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.contains(CounterEnumType.EXPERIENCE)) {
|
if (options.contains(CounterType.get(CounterEnumType.EXPERIENCE))) {
|
||||||
return CounterEnumType.EXPERIENCE;
|
return CounterType.get(CounterEnumType.EXPERIENCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1198,8 +1211,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
if (numCtrs < optimalCMC) {
|
if (numCtrs < optimalCMC) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
||||||
@@ -1219,8 +1233,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (numCtrs < optimalCMC) {
|
if (numCtrs < optimalCMC) {
|
||||||
// If the AI has less counters than the optimal CMC, it should play the ability.
|
// If the AI has less counters than the optimal CMC, it should play the ability.
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,18 +218,18 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
Card tgt = (Card) params.get("Target");
|
Card tgt = (Card) params.get("Target");
|
||||||
|
|
||||||
// planeswalker has high priority for loyalty counters
|
// planeswalker has high priority for loyalty counters
|
||||||
if (tgt.isPlaneswalker() && options.contains(CounterEnumType.LOYALTY)) {
|
if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
|
||||||
return CounterEnumType.LOYALTY;
|
return CounterType.get(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt.getController().isOpponentOf(ai)) {
|
if (tgt.getController().isOpponentOf(ai)) {
|
||||||
// creatures with BaseToughness below or equal zero might be
|
// creatures with BaseToughness below or equal zero might be
|
||||||
// killed if their counters are removed
|
// killed if their counters are removed
|
||||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||||
if (options.contains(CounterEnumType.P1P1)) {
|
if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||||
return CounterEnumType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
} else if (options.contains(CounterEnumType.M1M1)) {
|
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||||
return CounterEnumType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,17 +241,17 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// this counters are treat first to be removed
|
// this counters are treat first to be removed
|
||||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterEnumType.ICE)) {
|
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
|
||||||
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
||||||
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
|
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
|
||||||
|
|
||||||
if (maritEmpty) {
|
if (maritEmpty) {
|
||||||
return CounterEnumType.ICE;
|
return CounterType.get(CounterEnumType.ICE);
|
||||||
}
|
}
|
||||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterEnumType.P1P1)) {
|
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||||
return CounterEnumType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterEnumType.M1M1)) {
|
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||||
return CounterEnumType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback logic, select positive counter to add more
|
// fallback logic, select positive counter to add more
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (targetCard.getController().isOpponentOf(ai)) {
|
if (targetCard.getController().isOpponentOf(ai)) {
|
||||||
// if its a Planeswalker try to remove Loyality first
|
// if its a Planeswalker try to remove Loyality first
|
||||||
if (targetCard.isPlaneswalker()) {
|
if (targetCard.isPlaneswalker()) {
|
||||||
return CounterEnumType.LOYALTY;
|
return CounterType.get(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||||
@@ -392,10 +392,10 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.contains(CounterEnumType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||||
return CounterEnumType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
} else if (options.contains(CounterEnumType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
} else if (options.contains(CounterType.get(CounterEnumType.P1P1)) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||||
return CounterEnumType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||||
|
|||||||
@@ -133,9 +133,9 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
||||||
// When using Pestilence to hurt players, do it at
|
// When using Pestilence to hurt players, do it at
|
||||||
// the end of the opponent's turn only
|
// the end of the opponent's turn only
|
||||||
if (!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic"))
|
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|
||||||
|| (ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||||
&& !ai.getGame().getPhaseHandler().isPlayerTurn(ai)))
|
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
|
||||||
// Need further improvement : if able to kill immediately with repeated activations, do not wait
|
// Need further improvement : if able to kill immediately with repeated activations, do not wait
|
||||||
// for phases! Will also need to implement considering repeated activations for killed creatures!
|
// for phases! Will also need to implement considering repeated activations for killed creatures!
|
||||||
// || (ai.sa.getPayCosts(). ??? )
|
// || (ai.sa.getPayCosts(). ??? )
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.*;
|
import forge.game.cost.*;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -369,7 +370,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// try to make opponent lose to poison
|
// try to make opponent lose to poison
|
||||||
// currently only Caress of Phyrexia
|
// currently only Caress of Phyrexia
|
||||||
if (getPoison != null && oppA.canReceiveCounters(CounterEnumType.POISON)) {
|
if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
if (oppA.getPoisonCounters() + numCards > 9) {
|
if (oppA.getPoisonCounters() + numCards > 9) {
|
||||||
sa.getTargets().add(oppA);
|
sa.getTargets().add(oppA);
|
||||||
return true;
|
return true;
|
||||||
@@ -413,7 +414,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getPoison != null && ai.canReceiveCounters(CounterEnumType.POISON)) {
|
if (getPoison != null && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
if (numCards + ai.getPoisonCounters() >= 8) {
|
if (numCards + ai.getPoisonCounters() >= 8) {
|
||||||
aiTarget = false;
|
aiTarget = false;
|
||||||
}
|
}
|
||||||
@@ -471,7 +472,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ally would lose because of poison
|
// ally would lose because of poison
|
||||||
if (getPoison != null && ally.canReceiveCounters(CounterEnumType.POISON) && ally.getPoisonCounters() + numCards > 9) {
|
if (getPoison != null && ally.canReceiveCounters(CounterType.get(CounterEnumType.POISON)) && ally.getPoisonCounters() + numCards > 9) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("Fight")) {
|
} else if (logic.equals("Fight")) {
|
||||||
return FightAi.canFight(ai, sa, 0,0);
|
return FightAi.canFightAi(ai, sa, 0,0);
|
||||||
} else if (logic.equals("Pump")) {
|
} else if (logic.equals("Pump")) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
* @param power bonus to power
|
* @param power bonus to power
|
||||||
* @return true if fight effect should be played, false otherwise
|
* @return true if fight effect should be played, false otherwise
|
||||||
*/
|
*/
|
||||||
public static AiAbilityDecision canFight(final Player ai, final SpellAbility sa, int power, int toughness) {
|
public static AiAbilityDecision canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
AbilitySub tgtFight = sa.getSubAbility();
|
AbilitySub tgtFight = sa.getSubAbility();
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ import forge.game.spellability.SpellAbility;
|
|||||||
public class FlipACoinAi extends SpellAbilityAi {
|
public class FlipACoinAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#checkApiLogic(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String ailogic = sa.getParam("AILogic");
|
String ailogic = sa.getParam("AILogic");
|
||||||
if (ailogic.equals("PhaseOut")) {
|
if (ailogic.equals("Never")) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
} else if (ailogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class ManaAi extends SpellAbilityAi {
|
|||||||
int numCounters = 0;
|
int numCounters = 0;
|
||||||
int manaSurplus = 0;
|
int manaSurplus = 0;
|
||||||
if ("Count$xPaid".equals(host.getSVar("X")) && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
if ("Count$xPaid".equals(host.getSVar("X")) && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||||
CounterType ctrType = CounterEnumType.KI; // Petalmane Baku
|
CounterType ctrType = CounterType.get(CounterEnumType.KI); // Petalmane Baku
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
ctrType = ((CostRemoveCounter)part).counter;
|
ctrType = ((CostRemoveCounter)part).counter;
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
if (aiLogic.equals("LilianaMill")) {
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
|
||||||
|
if (aiLogic.equals("Main1")) {
|
||||||
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|
||||||
|
|| ComputerUtil.castSpellInMain1(ai, sa);
|
||||||
|
} else if (aiLogic.equals("LilianaMill")) {
|
||||||
// TODO convert to AICheckSVar
|
// TODO convert to AICheckSVar
|
||||||
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
||||||
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES).size() >= 1;
|
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES).size() >= 1;
|
||||||
@@ -50,10 +55,9 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
// because they are also potentially useful for combat
|
// because they are also potentially useful for combat
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
||||||
}
|
}
|
||||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|
return true;
|
||||||
|| ComputerUtil.castSpellInMain1(ai, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import forge.game.Game;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardUtil;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
@@ -38,6 +38,9 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
|
if (blocker == null) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
sa.getTargets().add(blocker);
|
sa.getTargets().add(blocker);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
@@ -60,6 +63,11 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
protected AiAbilityDecision doTriggerNoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
// only use on creatures that can attack
|
||||||
|
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
Card attacker = source;
|
Card attacker = source;
|
||||||
if (sa.hasParam("DefinedAttacker")) {
|
if (sa.hasParam("DefinedAttacker")) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
||||||
@@ -73,9 +81,13 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
final List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
||||||
if (list.isEmpty() && mandatory) {
|
if (list.isEmpty()) {
|
||||||
list = CardUtil.getValidCardsToTarget(sa);
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if (blocker == null) {
|
if (blocker == null) {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||||
&& ai.getManaPool().totalMana() <= 0
|
&& ai.getManaPool().totalMana() <= 0
|
||||||
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
|
&& (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()
|
&& game.getStack().isEmpty()
|
||||||
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
|
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
|
||||||
// AiPlayDecision.AnotherTime;
|
// AiPlayDecision.AnotherTime;
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ public class PhasesAi extends SpellAbilityAi {
|
|||||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
|
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
|
||||||
if (isThreatened) {
|
if (isThreatened) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import forge.ai.ComputerUtil;
|
|||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.GameLossReason;
|
import forge.game.player.GameLossReason;
|
||||||
@@ -64,7 +65,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
boolean result;
|
boolean result;
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
result = tgtPlayer(ai, sa, mandatory);
|
result = tgtPlayer(ai, sa, mandatory);
|
||||||
} else if (mandatory || !ai.canReceiveCounters(CounterEnumType.POISON)) {
|
} else if (mandatory || !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
// mandatory or ai is uneffected
|
// mandatory or ai is uneffected
|
||||||
result = true;
|
result = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -89,7 +90,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
PlayerCollection betterTgts = tgts.filter(input -> {
|
PlayerCollection betterTgts = tgts.filter(input -> {
|
||||||
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (!input.canReceiveCounters(CounterEnumType.POISON)) {
|
} else if (!input.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -108,7 +109,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
if (tgts.isEmpty()) {
|
if (tgts.isEmpty()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
// AI is uneffected
|
// AI is uneffected
|
||||||
if (ai.canBeTargetedBy(sa) && !ai.canReceiveCounters(CounterEnumType.POISON)) {
|
if (ai.canBeTargetedBy(sa) && !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -120,7 +121,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return !input.canReceiveCounters(CounterEnumType.POISON);
|
return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
|
||||||
});
|
});
|
||||||
if (!betterAllies.isEmpty()) {
|
if (!betterAllies.isEmpty()) {
|
||||||
allies = betterAllies;
|
allies = betterAllies;
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFight) {
|
if (isFight) {
|
||||||
return FightAi.canFight(ai, sa, attack, defense).willingToPlay();
|
return FightAi.canFightAi(ai, sa, attack, defense).willingToPlay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import forge.ai.SpellAbilityAi;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.player.PlayerController;
|
import forge.game.player.PlayerController;
|
||||||
@@ -39,7 +40,7 @@ public class TimeTravelAi extends SpellAbilityAi {
|
|||||||
// so removing them is good; stuff on the battlefield is usually stuff like Vanishing or As Foretold, which favors adding Time
|
// so removing them is good; stuff on the battlefield is usually stuff like Vanishing or As Foretold, which favors adding Time
|
||||||
// counters for better effect, but exceptions should be added here).
|
// counters for better effect, but exceptions should be added here).
|
||||||
Card target = (Card)params.get("Target");
|
Card target = (Card)params.get("Target");
|
||||||
return !ComputerUtil.isNegativeCounter(CounterEnumType.TIME, target);
|
return !ComputerUtil.isNegativeCounter(CounterType.get(CounterEnumType.TIME), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ public final class ImageKeys {
|
|||||||
public static final String MONARCH_IMAGE = "monarch";
|
public static final String MONARCH_IMAGE = "monarch";
|
||||||
public static final String THE_RING_IMAGE = "the_ring";
|
public static final String THE_RING_IMAGE = "the_ring";
|
||||||
public static final String RADIATION_IMAGE = "radiation";
|
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 BACKFACE_POSTFIX = "$alt";
|
||||||
public static final String SPECFACE_W = "$wspec";
|
public static final String SPECFACE_W = "$wspec";
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import java.util.stream.Collectors;
|
|||||||
public class StaticData {
|
public class StaticData {
|
||||||
private final CardStorageReader cardReader;
|
private final CardStorageReader cardReader;
|
||||||
private final CardStorageReader tokenReader;
|
private final CardStorageReader tokenReader;
|
||||||
|
private final CardStorageReader customCardReader;
|
||||||
|
|
||||||
private final String blockDataFolder;
|
private final String blockDataFolder;
|
||||||
private final CardDb commonCards;
|
private final CardDb commonCards;
|
||||||
private final CardDb variantCards;
|
private final CardDb variantCards;
|
||||||
@@ -77,6 +79,7 @@ public class StaticData {
|
|||||||
this.tokenReader = tokenReader;
|
this.tokenReader = tokenReader;
|
||||||
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
||||||
this.blockDataFolder = blockDataFolder;
|
this.blockDataFolder = blockDataFolder;
|
||||||
|
this.customCardReader = customCardReader;
|
||||||
this.allowCustomCardsInDecksConformance = allowCustomCardsInDecksConformance;
|
this.allowCustomCardsInDecksConformance = allowCustomCardsInDecksConformance;
|
||||||
this.enableSmartCardArtSelection = enableSmartCardArtSelection;
|
this.enableSmartCardArtSelection = enableSmartCardArtSelection;
|
||||||
this.loadNonLegalCards = loadNonLegalCards;
|
this.loadNonLegalCards = loadNonLegalCards;
|
||||||
@@ -878,7 +881,7 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||||
List<String> NIF = new ArrayList<>(NIF_Q).stream().sorted().collect(Collectors.toList());
|
List<String> NIF = new ArrayList<>(NIF_Q).stream().sorted().collect(Collectors.toList());
|
||||||
List<String> CNI = new ArrayList<>(CNI_Q).stream().sorted().collect(Collectors.toList());
|
List<String> CNI = new ArrayList<>(CNI_Q).stream().sorted().collect(Collectors.toList());
|
||||||
List<String> TOK = new ArrayList<>(TOKEN_Q).stream().sorted().collect(Collectors.toList());
|
List<String> TOK = new ArrayList<>(TOKEN_Q).stream().sorted().collect(Collectors.toList());
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
public final static char NameSetSeparator = '|';
|
public final static char NameSetSeparator = '|';
|
||||||
public final static String FlagPrefix = "#";
|
public final static String FlagPrefix = "#";
|
||||||
public static final String FlagSeparator = "\t";
|
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
|
// 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);
|
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
|
||||||
@@ -301,7 +303,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
// create faces list from rules
|
// create faces list from rules
|
||||||
for (final CardRules rule : rules.values()) {
|
for (final CardRules rule : rules.values()) {
|
||||||
if (filteredCards.contains(rule.getName()))
|
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
|
||||||
continue;
|
continue;
|
||||||
for (ICardFace face : rule.getAllFaces()) {
|
for (ICardFace face : rule.getAllFaces()) {
|
||||||
addFaceToDbNames(face);
|
addFaceToDbNames(face);
|
||||||
@@ -499,9 +501,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addCard(PaperCard paperCard) {
|
public void addCard(PaperCard paperCard) {
|
||||||
if (filtered.contains(paperCard.getName())) {
|
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
allCardsByName.put(paperCard.getName(), paperCard);
|
allCardsByName.put(paperCard.getName(), paperCard);
|
||||||
|
|
||||||
@@ -522,6 +523,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean excludeCard(String cardName, String cardEdition) {
|
||||||
|
if (filtered.isEmpty())
|
||||||
|
return false;
|
||||||
|
if (filtered.contains(cardName)) {
|
||||||
|
if (exlcudedCardSet.equalsIgnoreCase(cardEdition) && exlcudedCardName.equalsIgnoreCase(cardName))
|
||||||
|
return true;
|
||||||
|
else return !exlcudedCardName.equalsIgnoreCase(cardName);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void reIndex() {
|
private void reIndex() {
|
||||||
uniqueCardsByName.clear();
|
uniqueCardsByName.clear();
|
||||||
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
||||||
|
|||||||
@@ -52,14 +52,6 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public final class CardEdition implements Comparable<CardEdition> {
|
public final class CardEdition implements Comparable<CardEdition> {
|
||||||
|
|
||||||
public DraftOptions getDraftOptions() {
|
|
||||||
return draftOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDraftOptions(DraftOptions draftOptions) {
|
|
||||||
this.draftOptions = draftOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// immutable
|
// immutable
|
||||||
public enum Type {
|
public enum Type {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
@@ -283,22 +275,18 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
// Booster/draft info
|
// Booster/draft info
|
||||||
private List<BoosterSlot> boosterSlots = null;
|
private List<BoosterSlot> boosterSlots = null;
|
||||||
private boolean smallSetOverride = false;
|
private boolean smallSetOverride = false;
|
||||||
private String additionalUnlockSet = "";
|
|
||||||
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
|
||||||
|
|
||||||
// Replace all of these things with booster slots
|
|
||||||
private boolean foilAlwaysInCommonSlot = false;
|
private boolean foilAlwaysInCommonSlot = false;
|
||||||
|
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
||||||
private double foilChanceInBooster = 0;
|
private double foilChanceInBooster = 0;
|
||||||
private double chanceReplaceCommonWith = 0;
|
private double chanceReplaceCommonWith = 0;
|
||||||
private String slotReplaceCommonWith = "Common";
|
private String slotReplaceCommonWith = "Common";
|
||||||
private String additionalSheetForFoils = "";
|
private String additionalSheetForFoils = "";
|
||||||
|
private String additionalUnlockSet = "";
|
||||||
private String boosterMustContain = "";
|
private String boosterMustContain = "";
|
||||||
private String boosterReplaceSlotFromPrintSheet = "";
|
private String boosterReplaceSlotFromPrintSheet = "";
|
||||||
private String sheetReplaceCardFromSheet = "";
|
private String sheetReplaceCardFromSheet = "";
|
||||||
private String sheetReplaceCardFromSheet2 = "";
|
private String sheetReplaceCardFromSheet2 = "";
|
||||||
|
private String doublePickDuringDraft = "";
|
||||||
// Draft options
|
|
||||||
private DraftOptions draftOptions = null;
|
|
||||||
private String[] chaosDraftThemes = new String[0];
|
private String[] chaosDraftThemes = new String[0];
|
||||||
|
|
||||||
private final ListMultimap<String, EditionEntry> cardMap;
|
private final ListMultimap<String, EditionEntry> cardMap;
|
||||||
@@ -385,6 +373,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
|
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
|
||||||
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
||||||
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
||||||
|
public String getDoublePickDuringDraft() { return doublePickDuringDraft; }
|
||||||
public String getBoosterMustContain() { return boosterMustContain; }
|
public String getBoosterMustContain() { return boosterMustContain; }
|
||||||
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
||||||
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
|
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
|
||||||
@@ -630,7 +619,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
* functional variant name - grouping #9
|
* functional variant name - grouping #9
|
||||||
*/
|
*/
|
||||||
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
|
// "(^(.?[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(
|
final Pattern tokenPattern = Pattern.compile(
|
||||||
@@ -639,7 +628,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
* name - grouping #3
|
* name - grouping #3
|
||||||
* artist name - grouping #5
|
* artist name - grouping #5
|
||||||
*/
|
*/
|
||||||
"(^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]*)( @(.*))?$"
|
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
|
||||||
);
|
);
|
||||||
|
|
||||||
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
|
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
|
||||||
@@ -660,37 +649,31 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sectionName.endsWith("Types")) {
|
// parse sections of the format "<collector number> <rarity> <name>"
|
||||||
CardType.Helper.parseTypes(sectionName, contents.get(sectionName));
|
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
||||||
} else {
|
for(String line : contents.get(sectionName)) {
|
||||||
// Parse cards
|
Matcher matcher = pattern.matcher(line);
|
||||||
|
|
||||||
// parse sections of the format "<collector number> <rarity> <name>"
|
if (!matcher.matches()) {
|
||||||
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
continue;
|
||||||
for(String line : contents.get(sectionName)) {
|
|
||||||
Matcher matcher = pattern.matcher(line);
|
|
||||||
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String collectorNumber = matcher.group(2);
|
|
||||||
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
|
||||||
String cardName = matcher.group(5);
|
|
||||||
String artistName = matcher.group(7);
|
|
||||||
String functionalVariantName = matcher.group(9);
|
|
||||||
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
|
|
||||||
|
|
||||||
cardMap.put(sectionName, cis);
|
|
||||||
}
|
}
|
||||||
} else if (boosterSlotsToParse.contains(sectionName)) {
|
|
||||||
// parse booster slots of the format "Base=N\n|Replace=<amount> <sheet>"
|
String collectorNumber = matcher.group(2);
|
||||||
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
|
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
||||||
} else {
|
String cardName = matcher.group(5);
|
||||||
// save custom print sheets of the format "<amount> <name>|<setcode>|<art index>"
|
String artistName = matcher.group(7);
|
||||||
// to parse later when printsheets are loaded lazily (and the cardpool is already initialized)
|
String functionalVariantName = matcher.group(9);
|
||||||
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
|
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
|
||||||
|
|
||||||
|
cardMap.put(sectionName, cis);
|
||||||
}
|
}
|
||||||
|
} else if (boosterSlotsToParse.contains(sectionName)) {
|
||||||
|
// parse booster slots of the format "Base=N\n|Replace=<amount> <sheet>"
|
||||||
|
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
|
||||||
|
} else {
|
||||||
|
// save custom print sheets of the format "<amount> <name>|<setcode>|<art index>"
|
||||||
|
// to parse later when printsheets are loaded lazily (and the cardpool is already initialized)
|
||||||
|
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,6 +802,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
res.additionalUnlockSet = metadata.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
|
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.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.boosterMustContain = metadata.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
|
||||||
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
|
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
|
||||||
@@ -826,23 +810,6 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
|
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
|
||||||
res.chaosDraftThemes = metadata.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,7 +840,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
@Override
|
@Override
|
||||||
public void add(CardEdition item) { //Even though we want it to be read only, make an exception for custom content.
|
public void add(CardEdition item) { //Even though we want it to be read only, make an exception for custom content.
|
||||||
if(lock) throw new UnsupportedOperationException("This is a read-only storage");
|
if(lock) throw new UnsupportedOperationException("This is a read-only storage");
|
||||||
else map.put(item.getCode(), item);
|
else map.put(item.getName(), item);
|
||||||
}
|
}
|
||||||
public void append(CardEdition.Collection C){ //Append custom editions
|
public void append(CardEdition.Collection C){ //Append custom editions
|
||||||
if (lock) throw new UnsupportedOperationException("This is a read-only storage");
|
if (lock) throw new UnsupportedOperationException("This is a read-only storage");
|
||||||
@@ -1018,13 +985,16 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
|
|
||||||
public static final Predicate<CardEdition> HAS_BOOSTER_BOX = edition -> edition.getBoosterBoxCount() > 0;
|
public static final Predicate<CardEdition> HAS_BOOSTER_BOX = edition -> edition.getBoosterBoxCount() > 0;
|
||||||
|
|
||||||
@Deprecated //Use CardEdition::hasBasicLands and a nonnull test.
|
|
||||||
public static final Predicate<CardEdition> hasBasicLands = ed -> {
|
public static final Predicate<CardEdition> hasBasicLands = ed -> {
|
||||||
if (ed == null) {
|
if (ed == null) {
|
||||||
// Happens for new sets with "???" code
|
// Happens for new sets with "???" code
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return ed.hasBasicLands();
|
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||||
|
if (null == StaticData.instance().getCommonCards().getCard(landName, ed.getCode(), 0))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1045,7 +1015,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
|
|
||||||
public boolean hasBasicLands() {
|
public boolean hasBasicLands() {
|
||||||
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||||
if (this.getCardInSet(landName).isEmpty())
|
if (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
private boolean addsWildCardColor;
|
private boolean addsWildCardColor;
|
||||||
private int setColorID;
|
private int setColorID;
|
||||||
private boolean custom;
|
private boolean custom;
|
||||||
private boolean unsupported;
|
|
||||||
private String path;
|
private String path;
|
||||||
|
|
||||||
public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
|
public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
|
||||||
@@ -168,7 +167,21 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTransformable() {
|
public boolean isTransformable() {
|
||||||
return CardSplitType.Transform == getSplitType() || CardSplitType.Modal == getSplitType();
|
if (CardSplitType.Transform == getSplitType()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (CardSplitType.Modal != getSplitType()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (ICardFace face : getAllFaces()) {
|
||||||
|
for (String spell : face.getAbilities()) {
|
||||||
|
if (spell.contains("AB$ SetState") && spell.contains("Mode$ Transform")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO check keywords if needed
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICardFace getWSpecialize() {
|
public ICardFace getWSpecialize() {
|
||||||
@@ -207,9 +220,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCustom() { return custom; }
|
public boolean isCustom() { return custom; }
|
||||||
public void setCustom() { custom = true; }
|
public void setCustom() { custom = true; }
|
||||||
|
|
||||||
public boolean isUnsupported() { return unsupported; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardType getType() {
|
public CardType getType() {
|
||||||
@@ -324,12 +335,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
if (hasKeyword("Friends forever") && b.hasKeyword("Friends forever")) {
|
if (hasKeyword("Friends forever") && b.hasKeyword("Friends forever")) {
|
||||||
legal = true; // Stranger Things Secret Lair gimmick partner commander
|
legal = true; // Stranger Things Secret Lair gimmick partner commander
|
||||||
}
|
|
||||||
if (hasKeyword("Partner - Survivors") && b.hasKeyword("Partner - Survivors")) {
|
|
||||||
legal = true; // The Last of Us Secret Lair gimmick partner commander
|
|
||||||
}
|
|
||||||
if (hasKeyword("Partner - Father & Son") && b.hasKeyword("Partner - Father & Son")) {
|
|
||||||
legal = true; // God of War Secret Lair gimmick partner commander
|
|
||||||
}
|
}
|
||||||
if (hasKeyword("Choose a Background") && b.canBeBackground()
|
if (hasKeyword("Choose a Background") && b.canBeBackground()
|
||||||
|| b.hasKeyword("Choose a Background") && canBeBackground()) {
|
|| b.hasKeyword("Choose a Background") && canBeBackground()) {
|
||||||
@@ -348,7 +353,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
|
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
|
||||||
hasKeyword("Friends forever") || hasKeyword("Choose a Background") ||
|
hasKeyword("Friends forever") || hasKeyword("Choose a Background") ||
|
||||||
hasKeyword("Partner - Father & Son") || hasKeyword("Partner - Survivors") ||
|
|
||||||
hasKeyword("Doctor's companion") || isDoctor());
|
hasKeyword("Doctor's companion") || isDoctor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,9 +373,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
|
|
||||||
public boolean canBeOathbreaker() {
|
public boolean canBeOathbreaker() {
|
||||||
CardType type = mainPart.getType();
|
CardType type = mainPart.getType();
|
||||||
if (mainPart.getOracleText().contains("can be your commander")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return type.isPlaneswalker();
|
return type.isPlaneswalker();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,8 +825,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
faces[0].assignMissingFields();
|
faces[0].assignMissingFields();
|
||||||
final CardRules result = new CardRules(faces, CardSplitType.None, cah);
|
final CardRules result = new CardRules(faces, CardSplitType.None, cah);
|
||||||
|
|
||||||
result.unsupported = true;
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -196,31 +196,6 @@ public final class CardRulesPredicates {
|
|||||||
return card -> card.getSplitType().equals(type);
|
return card -> card.getSplitType().equals(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a Predicate that matches cards that are vanilla.
|
|
||||||
*/
|
|
||||||
public static Predicate<CardRules> isVanilla() {
|
|
||||||
return card -> {
|
|
||||||
if (!(card.getType().isCreature() || card.getType().isLand()) ||
|
|
||||||
card.getSplitType() != CardSplitType.None ||
|
|
||||||
card.hasFunctionalVariants()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ICardFace mainPart = card.getMainPart();
|
|
||||||
|
|
||||||
boolean hasAny =
|
|
||||||
mainPart.getKeywords().iterator().hasNext() ||
|
|
||||||
mainPart.getAbilities().iterator().hasNext() ||
|
|
||||||
mainPart.getStaticAbilities().iterator().hasNext() ||
|
|
||||||
mainPart.getTriggers().iterator().hasNext() ||
|
|
||||||
(mainPart.getDraftActions() != null && mainPart.getDraftActions().iterator().hasNext()) ||
|
|
||||||
mainPart.getReplacements().iterator().hasNext();
|
|
||||||
|
|
||||||
return !hasAny;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for color.
|
* Checks for color.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1066,74 +1066,4 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Helper {
|
|
||||||
public static final void parseTypes(String sectionName, List<String> content) {
|
|
||||||
Set<String> addToSection = null;
|
|
||||||
|
|
||||||
switch (sectionName) {
|
|
||||||
case "BasicTypes":
|
|
||||||
addToSection = CardType.Constant.BASIC_TYPES;
|
|
||||||
break;
|
|
||||||
case "LandTypes":
|
|
||||||
addToSection = CardType.Constant.LAND_TYPES;
|
|
||||||
break;
|
|
||||||
case "CreatureTypes":
|
|
||||||
addToSection = CardType.Constant.CREATURE_TYPES;
|
|
||||||
break;
|
|
||||||
case "SpellTypes":
|
|
||||||
addToSection = CardType.Constant.SPELL_TYPES;
|
|
||||||
break;
|
|
||||||
case "EnchantmentTypes":
|
|
||||||
addToSection = CardType.Constant.ENCHANTMENT_TYPES;
|
|
||||||
break;
|
|
||||||
case "ArtifactTypes":
|
|
||||||
addToSection = CardType.Constant.ARTIFACT_TYPES;
|
|
||||||
break;
|
|
||||||
case "WalkerTypes":
|
|
||||||
addToSection = CardType.Constant.WALKER_TYPES;
|
|
||||||
break;
|
|
||||||
case "DungeonTypes":
|
|
||||||
addToSection = CardType.Constant.DUNGEON_TYPES;
|
|
||||||
break;
|
|
||||||
case "BattleTypes":
|
|
||||||
addToSection = CardType.Constant.BATTLE_TYPES;
|
|
||||||
break;
|
|
||||||
case "PlanarTypes":
|
|
||||||
addToSection = CardType.Constant.PLANAR_TYPES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addToSection == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(String line : content) {
|
|
||||||
if (line.length() == 0) continue;
|
|
||||||
|
|
||||||
if (line.contains(":")) {
|
|
||||||
String[] k = line.split(":");
|
|
||||||
|
|
||||||
if (addToSection.contains(k[0])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToSection.add(k[0]);
|
|
||||||
CardType.Constant.pluralTypes.put(k[0], k[1]);
|
|
||||||
|
|
||||||
if (k[0].contains(" ")) {
|
|
||||||
CardType.Constant.MultiwordTypes.add(k[0]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (addToSection.contains(line)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToSection.add(line);
|
|
||||||
if (line.contains(" ")) {
|
|
||||||
CardType.Constant.MultiwordTypes.add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package forge.card;
|
|||||||
import com.google.common.collect.UnmodifiableIterator;
|
import com.google.common.collect.UnmodifiableIterator;
|
||||||
import forge.card.MagicColor.Color;
|
import forge.card.MagicColor.Color;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
|
import forge.card.mana.ManaCostShard;
|
||||||
import forge.util.BinaryUtil;
|
import forge.util.BinaryUtil;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -40,95 +41,25 @@ import java.util.stream.Stream;
|
|||||||
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
|
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
|
||||||
private static final long serialVersionUID = 794691267379929080L;
|
private static final long serialVersionUID = 794691267379929080L;
|
||||||
|
|
||||||
// needs to be before other static
|
|
||||||
private static final ColorSet[] cache = new ColorSet[MagicColor.ALL_COLORS + 1];
|
|
||||||
static {
|
|
||||||
byte COLORLESS = MagicColor.COLORLESS;
|
|
||||||
byte WHITE = MagicColor.WHITE;
|
|
||||||
byte BLUE = MagicColor.BLUE;
|
|
||||||
byte BLACK = MagicColor.BLACK;
|
|
||||||
byte RED = MagicColor.RED;
|
|
||||||
byte GREEN = MagicColor.GREEN;
|
|
||||||
Color C = Color.COLORLESS;
|
|
||||||
Color W = Color.WHITE;
|
|
||||||
Color U = Color.BLUE;
|
|
||||||
Color B = Color.BLACK;
|
|
||||||
Color R = Color.RED;
|
|
||||||
Color G = Color.GREEN;
|
|
||||||
|
|
||||||
//colorless
|
|
||||||
cache[COLORLESS] = new ColorSet(C);
|
|
||||||
|
|
||||||
//mono-color
|
|
||||||
cache[WHITE] = new ColorSet(W);
|
|
||||||
cache[BLUE] = new ColorSet(U);
|
|
||||||
cache[BLACK] = new ColorSet(B);
|
|
||||||
cache[RED] = new ColorSet(R);
|
|
||||||
cache[GREEN] = new ColorSet(G);
|
|
||||||
|
|
||||||
//two-color
|
|
||||||
cache[WHITE | BLUE] = new ColorSet(W, U);
|
|
||||||
cache[WHITE | BLACK] = new ColorSet(W, B);
|
|
||||||
cache[BLUE | BLACK] = new ColorSet(U, B);
|
|
||||||
cache[BLUE | RED] = new ColorSet(U, R);
|
|
||||||
cache[BLACK | RED] = new ColorSet(B, R);
|
|
||||||
cache[BLACK | GREEN] = new ColorSet(B, G);
|
|
||||||
cache[RED | GREEN] = new ColorSet(R, G);
|
|
||||||
cache[RED | WHITE] = new ColorSet(R, W);
|
|
||||||
cache[GREEN | WHITE] = new ColorSet(G, W);
|
|
||||||
cache[GREEN | BLUE] = new ColorSet(G, U);
|
|
||||||
|
|
||||||
//three-color
|
|
||||||
cache[WHITE | BLUE | BLACK] = new ColorSet(W, U, B);
|
|
||||||
cache[WHITE | BLACK | GREEN] = new ColorSet(W, B, G);
|
|
||||||
cache[BLUE | BLACK | RED] = new ColorSet(U, B, R);
|
|
||||||
cache[BLUE | RED | WHITE] = new ColorSet(U, R, W);
|
|
||||||
cache[BLACK | RED | GREEN] = new ColorSet(B, R, G);
|
|
||||||
cache[BLACK | GREEN | BLUE] = new ColorSet(B, G, U);
|
|
||||||
cache[RED | GREEN | WHITE] = new ColorSet(R, G, W);
|
|
||||||
cache[RED | WHITE | BLACK] = new ColorSet(R, W, B);
|
|
||||||
cache[GREEN | WHITE | BLUE] = new ColorSet(G, W, U);
|
|
||||||
cache[GREEN | BLUE | RED] = new ColorSet(G, U, R);
|
|
||||||
|
|
||||||
//four-color
|
|
||||||
cache[WHITE | BLUE | BLACK | RED] = new ColorSet(W, U, B, R);
|
|
||||||
cache[BLUE | BLACK | RED | GREEN] = new ColorSet(U, B, R, G);
|
|
||||||
cache[BLACK | RED | GREEN | WHITE] = new ColorSet(B, R, G, W);
|
|
||||||
cache[RED | GREEN | WHITE | BLUE] = new ColorSet(R, G, W, U);
|
|
||||||
cache[GREEN | WHITE | BLUE | BLACK] = new ColorSet(G, W, U, B);
|
|
||||||
|
|
||||||
//five-color
|
|
||||||
cache[WHITE | BLUE | BLACK | RED | GREEN] = new ColorSet(W, U, B, R, G);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Collection<Color> orderedShards;
|
|
||||||
private final byte myColor;
|
private final byte myColor;
|
||||||
private final float orderWeight;
|
private final float orderWeight;
|
||||||
private final Set<Color> enumSet;
|
|
||||||
private final String desc;
|
private static final ColorSet[] cache = new ColorSet[32];
|
||||||
|
|
||||||
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
|
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
|
||||||
public static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
private static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
||||||
|
|
||||||
private ColorSet(final Color... ordered) {
|
private ColorSet(final byte mask) {
|
||||||
this.orderedShards = Arrays.asList(ordered);
|
this.myColor = mask;
|
||||||
this.myColor = orderedShards.stream().map(Color::getColorMask).reduce((byte)0, (a, b) -> (byte)(a | b));
|
|
||||||
this.orderWeight = this.getOrderWeight();
|
this.orderWeight = this.getOrderWeight();
|
||||||
this.enumSet = EnumSet.copyOf(orderedShards);
|
|
||||||
this.desc = orderedShards.stream().map(Color::getShortName).collect(Collectors.joining());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ColorSet fromMask(final int mask) {
|
public static ColorSet fromMask(final int mask) {
|
||||||
final int mask32 = mask & MagicColor.ALL_COLORS;
|
final int mask32 = mask & MagicColor.ALL_COLORS;
|
||||||
return cache[mask32];
|
if (cache[mask32] == null) {
|
||||||
}
|
cache[mask32] = new ColorSet((byte) mask32);
|
||||||
|
|
||||||
public static ColorSet fromEnums(final Color... colors) {
|
|
||||||
byte mask = 0;
|
|
||||||
for (Color e : colors) {
|
|
||||||
mask |= e.getColorMask();
|
|
||||||
}
|
}
|
||||||
return fromMask(mask);
|
return cache[mask32];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ColorSet fromNames(final String... colors) {
|
public static ColorSet fromNames(final String... colors) {
|
||||||
@@ -362,7 +293,17 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return desc;
|
final ManaCostShard[] orderedShards = getOrderedShards();
|
||||||
|
return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the null color.
|
||||||
|
*
|
||||||
|
* @return the nullColor
|
||||||
|
*/
|
||||||
|
public static ColorSet getNullColor() {
|
||||||
|
return NO_COLORS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -384,7 +325,16 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Set<Color> toEnumSet() {
|
public Set<Color> toEnumSet() {
|
||||||
return EnumSet.copyOf(enumSet);
|
if (isColorless()) {
|
||||||
|
return EnumSet.of(Color.COLORLESS);
|
||||||
|
}
|
||||||
|
List<Color> list = new ArrayList<>();
|
||||||
|
for (Color c : Color.values()) {
|
||||||
|
if (hasAnyColor(c.getColormask())) {
|
||||||
|
list.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EnumSet.copyOf(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -422,12 +372,72 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<Color> stream() {
|
public Stream<MagicColor.Color> stream() {
|
||||||
return this.toEnumSet().stream();
|
return this.toEnumSet().stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get array of mana cost shards for color set in the proper order
|
//Get array of mana cost shards for color set in the proper order
|
||||||
public Collection<Color> getOrderedColors() {
|
public ManaCostShard[] getOrderedShards() {
|
||||||
return orderedShards;
|
return shardOrderLookup[myColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ManaCostShard[][] shardOrderLookup = new ManaCostShard[MagicColor.ALL_COLORS + 1][];
|
||||||
|
static {
|
||||||
|
byte COLORLESS = MagicColor.COLORLESS;
|
||||||
|
byte WHITE = MagicColor.WHITE;
|
||||||
|
byte BLUE = MagicColor.BLUE;
|
||||||
|
byte BLACK = MagicColor.BLACK;
|
||||||
|
byte RED = MagicColor.RED;
|
||||||
|
byte GREEN = MagicColor.GREEN;
|
||||||
|
ManaCostShard C = ManaCostShard.COLORLESS;
|
||||||
|
ManaCostShard W = ManaCostShard.WHITE;
|
||||||
|
ManaCostShard U = ManaCostShard.BLUE;
|
||||||
|
ManaCostShard B = ManaCostShard.BLACK;
|
||||||
|
ManaCostShard R = ManaCostShard.RED;
|
||||||
|
ManaCostShard G = ManaCostShard.GREEN;
|
||||||
|
|
||||||
|
//colorless
|
||||||
|
shardOrderLookup[COLORLESS] = new ManaCostShard[] { C };
|
||||||
|
|
||||||
|
//mono-color
|
||||||
|
shardOrderLookup[WHITE] = new ManaCostShard[] { W };
|
||||||
|
shardOrderLookup[BLUE] = new ManaCostShard[] { U };
|
||||||
|
shardOrderLookup[BLACK] = new ManaCostShard[] { B };
|
||||||
|
shardOrderLookup[RED] = new ManaCostShard[] { R };
|
||||||
|
shardOrderLookup[GREEN] = new ManaCostShard[] { G };
|
||||||
|
|
||||||
|
//two-color
|
||||||
|
shardOrderLookup[WHITE | BLUE] = new ManaCostShard[] { W, U };
|
||||||
|
shardOrderLookup[WHITE | BLACK] = new ManaCostShard[] { W, B };
|
||||||
|
shardOrderLookup[BLUE | BLACK] = new ManaCostShard[] { U, B };
|
||||||
|
shardOrderLookup[BLUE | RED] = new ManaCostShard[] { U, R };
|
||||||
|
shardOrderLookup[BLACK | RED] = new ManaCostShard[] { B, R };
|
||||||
|
shardOrderLookup[BLACK | GREEN] = new ManaCostShard[] { B, G };
|
||||||
|
shardOrderLookup[RED | GREEN] = new ManaCostShard[] { R, G };
|
||||||
|
shardOrderLookup[RED | WHITE] = new ManaCostShard[] { R, W };
|
||||||
|
shardOrderLookup[GREEN | WHITE] = new ManaCostShard[] { G, W };
|
||||||
|
shardOrderLookup[GREEN | BLUE] = new ManaCostShard[] { G, U };
|
||||||
|
|
||||||
|
//three-color
|
||||||
|
shardOrderLookup[WHITE | BLUE | BLACK] = new ManaCostShard[] { W, U, B };
|
||||||
|
shardOrderLookup[WHITE | BLACK | GREEN] = new ManaCostShard[] { W, B, G };
|
||||||
|
shardOrderLookup[BLUE | BLACK | RED] = new ManaCostShard[] { U, B, R };
|
||||||
|
shardOrderLookup[BLUE | RED | WHITE] = new ManaCostShard[] { U, R, W };
|
||||||
|
shardOrderLookup[BLACK | RED | GREEN] = new ManaCostShard[] { B, R, G };
|
||||||
|
shardOrderLookup[BLACK | GREEN | BLUE] = new ManaCostShard[] { B, G, U };
|
||||||
|
shardOrderLookup[RED | GREEN | WHITE] = new ManaCostShard[] { R, G, W };
|
||||||
|
shardOrderLookup[RED | WHITE | BLACK] = new ManaCostShard[] { R, W, B };
|
||||||
|
shardOrderLookup[GREEN | WHITE | BLUE] = new ManaCostShard[] { G, W, U };
|
||||||
|
shardOrderLookup[GREEN | BLUE | RED] = new ManaCostShard[] { G, U, R };
|
||||||
|
|
||||||
|
//four-color
|
||||||
|
shardOrderLookup[WHITE | BLUE | BLACK | RED] = new ManaCostShard[] { W, U, B, R };
|
||||||
|
shardOrderLookup[BLUE | BLACK | RED | GREEN] = new ManaCostShard[] { U, B, R, G };
|
||||||
|
shardOrderLookup[BLACK | RED | GREEN | WHITE] = new ManaCostShard[] { B, R, G, W };
|
||||||
|
shardOrderLookup[RED | GREEN | WHITE | BLUE] = new ManaCostShard[] { R, G, W, U };
|
||||||
|
shardOrderLookup[GREEN | WHITE | BLUE | BLACK] = new ManaCostShard[] { G, W, U, B };
|
||||||
|
|
||||||
|
//five-color
|
||||||
|
shardOrderLookup[WHITE | BLUE | BLACK | RED | GREEN] = new ManaCostShard[] { W, U, B, R, G };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package forge.card;
|
|
||||||
|
|
||||||
public class DraftOptions {
|
|
||||||
public enum DoublePick {
|
|
||||||
NEVER,
|
|
||||||
FIRST_PICK, // only first pick each pack
|
|
||||||
WHEN_POD_SIZE_IS_4, // only when pod size is 4, so you can pick two cards each time
|
|
||||||
ALWAYS // each time you receive a pack, you can pick two cards
|
|
||||||
};
|
|
||||||
public enum DeckType {
|
|
||||||
Normal, // Standard deck, usually 40 cards
|
|
||||||
Commander // Special deck type for Commander format. Important for selection/construction
|
|
||||||
}
|
|
||||||
|
|
||||||
private DoublePick doublePick = DoublePick.NEVER;
|
|
||||||
private final int maxPodSize; // Usually 8, but could be smaller for cubes. I guess it could be larger too
|
|
||||||
private final int recommendedPodSize; // Usually 8, but is 4 for new double pick
|
|
||||||
private final int maxMatchPlayers; // Usually 2, but 4 for things like Commander or Conspiracy
|
|
||||||
private final DeckType deckType; // Normal or Commander
|
|
||||||
private final String freeCommander;
|
|
||||||
|
|
||||||
public DraftOptions(String doublePickOption, int maxPodSize, int recommendedPodSize, int maxMatchPlayers, String deckType, String freeCommander) {
|
|
||||||
this.maxPodSize = maxPodSize;
|
|
||||||
this.recommendedPodSize = recommendedPodSize;
|
|
||||||
this.maxMatchPlayers = maxMatchPlayers;
|
|
||||||
this.deckType = DeckType.valueOf(deckType);
|
|
||||||
this.freeCommander = freeCommander;
|
|
||||||
if (doublePickOption != null) {
|
|
||||||
switch (doublePickOption.toLowerCase()) {
|
|
||||||
case "firstpick":
|
|
||||||
doublePick = DoublePick.FIRST_PICK;
|
|
||||||
break;
|
|
||||||
case "always":
|
|
||||||
doublePick = DoublePick.ALWAYS;
|
|
||||||
break;
|
|
||||||
case "whenpodsizeis4":
|
|
||||||
doublePick = DoublePick.WHEN_POD_SIZE_IS_4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
public int getMaxPodSize() {
|
|
||||||
return maxPodSize;
|
|
||||||
}
|
|
||||||
public int getRecommendedPodSize() {
|
|
||||||
return recommendedPodSize;
|
|
||||||
}
|
|
||||||
public DoublePick getDoublePick() {
|
|
||||||
return doublePick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DoublePick isDoublePick(int podSize) {
|
|
||||||
if (doublePick == DoublePick.WHEN_POD_SIZE_IS_4) {
|
|
||||||
if (podSize != 4) {
|
|
||||||
return DoublePick.NEVER;
|
|
||||||
}
|
|
||||||
// only when pod size is 4, so you can pick two cards each time
|
|
||||||
return DoublePick.ALWAYS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return doublePick;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getMaxMatchPlayers() {
|
|
||||||
return maxMatchPlayers;
|
|
||||||
}
|
|
||||||
public DeckType getDeckType() {
|
|
||||||
return deckType;
|
|
||||||
}
|
|
||||||
public String getFreeCommander() {
|
|
||||||
return freeCommander;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import forge.deck.DeckRecognizer;
|
||||||
import forge.util.ITranslatable;
|
|
||||||
import forge.util.Localizer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds byte values for each color magic has.
|
* Holds byte values for each color magic has.
|
||||||
@@ -159,24 +157,21 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Color implements ITranslatable {
|
public enum Color {
|
||||||
WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"),
|
WHITE(Constant.WHITE, MagicColor.WHITE, "{W}"),
|
||||||
BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"),
|
BLUE(Constant.BLUE, MagicColor.BLUE, "{U}"),
|
||||||
BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"),
|
BLACK(Constant.BLACK, MagicColor.BLACK, "{B}"),
|
||||||
RED(Constant.RED, MagicColor.RED, "R", "lblRed"),
|
RED(Constant.RED, MagicColor.RED, "{R}"),
|
||||||
GREEN(Constant.GREEN, MagicColor.GREEN, "G", "lblGreen"),
|
GREEN(Constant.GREEN, MagicColor.GREEN, "{G}"),
|
||||||
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "C", "lblColorless");
|
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "{C}");
|
||||||
|
|
||||||
private final String name, shortName, symbol;
|
private final String name, symbol;
|
||||||
private final String label;
|
|
||||||
private final byte colormask;
|
private final byte colormask;
|
||||||
|
|
||||||
Color(String name0, byte colormask0, String shortName, String label) {
|
Color(String name0, byte colormask0, String symbol0) {
|
||||||
name = name0;
|
name = name0;
|
||||||
colormask = colormask0;
|
colormask = colormask0;
|
||||||
this.shortName = shortName;
|
symbol = symbol0;
|
||||||
symbol = "{" + shortName + "}";
|
|
||||||
this.label = label;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color fromByte(final byte color) {
|
public static Color fromByte(final byte color) {
|
||||||
@@ -190,25 +185,25 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
public String getShortName() {
|
|
||||||
return shortName;
|
public String getLocalizedName() {
|
||||||
|
//Should probably move some of this logic back here, or at least to a more general location.
|
||||||
|
return DeckRecognizer.getLocalisedMagicColorName(getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public byte getColormask() {
|
||||||
public String getTranslatedName() {
|
|
||||||
return Localizer.getInstance().getMessage(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getColorMask() {
|
|
||||||
return colormask;
|
return colormask;
|
||||||
}
|
}
|
||||||
public String getSymbol() {
|
public String getSymbol() {
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,20 +115,6 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
return parts.get(DeckSection.Main);
|
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() {
|
public List<PaperCard> getCommanders() {
|
||||||
List<PaperCard> result = Lists.newArrayList();
|
List<PaperCard> result = Lists.newArrayList();
|
||||||
final CardPool cp = get(DeckSection.Commander);
|
final CardPool cp = get(DeckSection.Commander);
|
||||||
|
|||||||
@@ -49,16 +49,6 @@ public class DeckRecognizer {
|
|||||||
LIMITED_CARD,
|
LIMITED_CARD,
|
||||||
CARD_FROM_NOT_ALLOWED_SET,
|
CARD_FROM_NOT_ALLOWED_SET,
|
||||||
CARD_FROM_INVALID_SET,
|
CARD_FROM_INVALID_SET,
|
||||||
/**
|
|
||||||
* Valid card request, but can't be imported because the player does not have enough copies.
|
|
||||||
* Should be replaced with a different printing if possible.
|
|
||||||
*/
|
|
||||||
CARD_NOT_IN_INVENTORY,
|
|
||||||
/**
|
|
||||||
* Valid card request for a card that isn't in the player's inventory, but new copies can be acquired freely.
|
|
||||||
* Usually used for basic lands. Should be supplied to the import controller by the editor.
|
|
||||||
*/
|
|
||||||
FREE_CARD_NOT_IN_INVENTORY,
|
|
||||||
// Warning messages
|
// Warning messages
|
||||||
WARNING_MESSAGE,
|
WARNING_MESSAGE,
|
||||||
UNKNOWN_CARD,
|
UNKNOWN_CARD,
|
||||||
@@ -73,14 +63,10 @@ public class DeckRecognizer {
|
|||||||
CARD_TYPE,
|
CARD_TYPE,
|
||||||
CARD_RARITY,
|
CARD_RARITY,
|
||||||
CARD_CMC,
|
CARD_CMC,
|
||||||
MANA_COLOUR;
|
MANA_COLOUR
|
||||||
|
|
||||||
public static final EnumSet<TokenType> CARD_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET, CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY);
|
|
||||||
public static final EnumSet<TokenType> IN_DECK_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, DECK_NAME, FREE_CARD_NOT_IN_INVENTORY);
|
|
||||||
public static final EnumSet<TokenType> CARD_PLACEHOLDER_TOKEN_TYPES = EnumSet.of(CARD_TYPE, CARD_RARITY, CARD_CMC, MANA_COLOUR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum LimitedCardType {
|
public enum LimitedCardType{
|
||||||
BANNED,
|
BANNED,
|
||||||
RESTRICTED,
|
RESTRICTED,
|
||||||
}
|
}
|
||||||
@@ -122,10 +108,6 @@ public class DeckRecognizer {
|
|||||||
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
|
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Token NotInInventoryFree(final PaperCard card, final int count, final DeckSection section) {
|
|
||||||
return new Token(TokenType.FREE_CARD_NOT_IN_INVENTORY, count, card, section, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING MESSAGES
|
// WARNING MESSAGES
|
||||||
// ================
|
// ================
|
||||||
public static Token UnknownCard(final String cardName, final String setCode, final int count) {
|
public static Token UnknownCard(final String cardName, final String setCode, final int count) {
|
||||||
@@ -144,10 +126,6 @@ public class DeckRecognizer {
|
|||||||
return new Token(TokenType.WARNING_MESSAGE, msg);
|
return new Token(TokenType.WARNING_MESSAGE, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Token NotInInventory(final PaperCard card, final int count, final DeckSection section) {
|
|
||||||
return new Token(TokenType.CARD_NOT_IN_INVENTORY, count, card, section, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================================
|
/* =================================
|
||||||
* DECK SECTIONS
|
* DECK SECTIONS
|
||||||
* ================================= */
|
* ================================= */
|
||||||
@@ -261,11 +239,14 @@ public class DeckRecognizer {
|
|||||||
/**
|
/**
|
||||||
* Filters all token types that have a PaperCard instance set (not null)
|
* Filters all token types that have a PaperCard instance set (not null)
|
||||||
* @return true for tokens of type:
|
* @return true for tokens of type:
|
||||||
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY.
|
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET.
|
||||||
* False otherwise.
|
* False otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isCardToken() {
|
public boolean isCardToken() {
|
||||||
return TokenType.CARD_TOKEN_TYPES.contains(this.type);
|
return (this.type == TokenType.LEGAL_CARD ||
|
||||||
|
this.type == TokenType.LIMITED_CARD ||
|
||||||
|
this.type == TokenType.CARD_FROM_NOT_ALLOWED_SET ||
|
||||||
|
this.type == TokenType.CARD_FROM_INVALID_SET);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -274,7 +255,9 @@ public class DeckRecognizer {
|
|||||||
* LEGAL_CARD, LIMITED_CARD, DECK_NAME; false otherwise.
|
* LEGAL_CARD, LIMITED_CARD, DECK_NAME; false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isTokenForDeck() {
|
public boolean isTokenForDeck() {
|
||||||
return TokenType.IN_DECK_TOKEN_TYPES.contains(this.type);
|
return (this.type == TokenType.LEGAL_CARD ||
|
||||||
|
this.type == TokenType.LIMITED_CARD ||
|
||||||
|
this.type == TokenType.DECK_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -283,7 +266,7 @@ public class DeckRecognizer {
|
|||||||
* False otherwise.
|
* False otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isCardTokenForDeck() {
|
public boolean isCardTokenForDeck() {
|
||||||
return isCardToken() && isTokenForDeck();
|
return (this.type == TokenType.LEGAL_CARD || this.type == TokenType.LIMITED_CARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -293,7 +276,10 @@ public class DeckRecognizer {
|
|||||||
* CARD_RARITY, CARD_CMC, CARD_TYPE, MANA_COLOUR
|
* CARD_RARITY, CARD_CMC, CARD_TYPE, MANA_COLOUR
|
||||||
*/
|
*/
|
||||||
public boolean isCardPlaceholder(){
|
public boolean isCardPlaceholder(){
|
||||||
return TokenType.CARD_PLACEHOLDER_TOKEN_TYPES.contains(this.type);
|
return (this.type == TokenType.CARD_RARITY ||
|
||||||
|
this.type == TokenType.CARD_CMC ||
|
||||||
|
this.type == TokenType.MANA_COLOUR ||
|
||||||
|
this.type == TokenType.CARD_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Determines if current token is a Deck Section token
|
/** Determines if current token is a Deck Section token
|
||||||
@@ -550,7 +536,7 @@ public class DeckRecognizer {
|
|||||||
PaperCard tokenCard = token.getCard();
|
PaperCard tokenCard = token.getCard();
|
||||||
|
|
||||||
if (isAllowed(tokenSection)) {
|
if (isAllowed(tokenSection)) {
|
||||||
if (tokenSection != referenceDeckSectionInParsing) {
|
if (!tokenSection.equals(referenceDeckSectionInParsing)) {
|
||||||
Token sectionToken = Token.DeckSection(tokenSection.name(), this.allowedDeckSections);
|
Token sectionToken = Token.DeckSection(tokenSection.name(), this.allowedDeckSections);
|
||||||
// just check that last token is stack is a card placeholder.
|
// just check that last token is stack is a card placeholder.
|
||||||
// In that case, add the new section token before the placeholder
|
// In that case, add the new section token before the placeholder
|
||||||
@@ -589,7 +575,7 @@ public class DeckRecognizer {
|
|||||||
refLine = purgeAllLinks(refLine);
|
refLine = purgeAllLinks(refLine);
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
if (refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
if (StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
||||||
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
|
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
|
||||||
else
|
else
|
||||||
line = refLine.trim(); // Remove any trailing formatting
|
line = refLine.trim(); // Remove any trailing formatting
|
||||||
@@ -598,7 +584,7 @@ public class DeckRecognizer {
|
|||||||
// Final fantasy cards like Summon: Choco/Mog should be ommited to be recognized. TODO: fix maybe for future cards
|
// Final fantasy cards like Summon: Choco/Mog should be ommited to be recognized. TODO: fix maybe for future cards
|
||||||
if (!line.contains("Summon:"))
|
if (!line.contains("Summon:"))
|
||||||
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
|
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
|
||||||
if (line.startsWith(ASTERISK)) // Markdown lists (tappedout md export)
|
if (StringUtils.startsWith(line, ASTERISK)) // markdown lists (tappedout md export)
|
||||||
line = line.substring(2);
|
line = line.substring(2);
|
||||||
|
|
||||||
// == Patches to Corner Cases
|
// == Patches to Corner Cases
|
||||||
@@ -614,8 +600,8 @@ public class DeckRecognizer {
|
|||||||
Token result = recogniseCardToken(line, referenceSection);
|
Token result = recogniseCardToken(line, referenceSection);
|
||||||
if (result == null)
|
if (result == null)
|
||||||
result = recogniseNonCardToken(line);
|
result = recogniseNonCardToken(line);
|
||||||
return result != null ? result : refLine.startsWith(DOUBLE_SLASH) ||
|
return result != null ? result : StringUtils.startsWith(refLine, DOUBLE_SLASH) ||
|
||||||
refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
||||||
new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine);
|
new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,7 +613,7 @@ public class DeckRecognizer {
|
|||||||
while (m.find()) {
|
while (m.find()) {
|
||||||
line = line.replaceAll(m.group(), "").trim();
|
line = line.replaceAll(m.group(), "").trim();
|
||||||
}
|
}
|
||||||
if (line.endsWith("()"))
|
if (StringUtils.endsWith(line, "()"))
|
||||||
return line.substring(0, line.length()-2);
|
return line.substring(0, line.length()-2);
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -755,12 +741,21 @@ public class DeckRecognizer {
|
|||||||
// This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand
|
// This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand
|
||||||
private DeckSection getTokenSection(String deckSec, DeckSection currentDeckSection, PaperCard card){
|
private DeckSection getTokenSection(String deckSec, DeckSection currentDeckSection, PaperCard card){
|
||||||
if (deckSec != null) {
|
if (deckSec != null) {
|
||||||
DeckSection cardSection = switch (deckSec.toUpperCase().trim()) {
|
DeckSection cardSection;
|
||||||
case "MB" -> DeckSection.Main;
|
switch (deckSec.toUpperCase().trim()) {
|
||||||
case "SB" -> DeckSection.Sideboard;
|
case "MB":
|
||||||
case "CM" -> DeckSection.Commander;
|
cardSection = DeckSection.Main;
|
||||||
default -> DeckSection.matchingSection(card);
|
break;
|
||||||
};
|
case "SB":
|
||||||
|
cardSection = DeckSection.Sideboard;
|
||||||
|
break;
|
||||||
|
case "CM":
|
||||||
|
cardSection = DeckSection.Commander;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cardSection = DeckSection.matchingSection(card);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (cardSection.validate(card))
|
if (cardSection.validate(card))
|
||||||
return cardSection;
|
return cardSection;
|
||||||
}
|
}
|
||||||
@@ -994,7 +989,7 @@ public class DeckRecognizer {
|
|||||||
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
||||||
if (magicColor == null) // Multicolour
|
if (magicColor == null) // Multicolour
|
||||||
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
||||||
return String.format("%s %s", magicColor.getTranslatedName(), magicColor.getSymbol());
|
return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
||||||
@@ -1013,30 +1008,60 @@ public class DeckRecognizer {
|
|||||||
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
||||||
|| magicColor1 == MagicColor.Color.COLORLESS)
|
|| magicColor1 == MagicColor.Color.COLORLESS)
|
||||||
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
||||||
String localisedName1 = magicColor1.getTranslatedName();
|
String localisedName1 = magicColor1.getLocalizedName();
|
||||||
String localisedName2 = magicColor2.getTranslatedName();
|
String localisedName2 = magicColor2.getLocalizedName();
|
||||||
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColorMask() | magicColor2.getColorMask());
|
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
|
||||||
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MagicColor.Color getMagicColor(String colorName){
|
private static MagicColor.Color getMagicColor(String colorName){
|
||||||
if (colorName.toLowerCase().startsWith("multi") || colorName.equalsIgnoreCase("m"))
|
if (colorName.toLowerCase().startsWith("multi") || colorName.equalsIgnoreCase("m"))
|
||||||
return null; // will be handled separately
|
return null; // will be handled separately
|
||||||
return MagicColor.Color.fromByte(MagicColor.fromName(colorName.toLowerCase()));
|
|
||||||
|
byte color = MagicColor.fromName(colorName.toLowerCase());
|
||||||
|
switch (color) {
|
||||||
|
case MagicColor.WHITE:
|
||||||
|
return MagicColor.Color.WHITE;
|
||||||
|
case MagicColor.BLUE:
|
||||||
|
return MagicColor.Color.BLUE;
|
||||||
|
case MagicColor.BLACK:
|
||||||
|
return MagicColor.Color.BLACK;
|
||||||
|
case MagicColor.RED:
|
||||||
|
return MagicColor.Color.RED;
|
||||||
|
case MagicColor.GREEN:
|
||||||
|
return MagicColor.Color.GREEN;
|
||||||
|
default:
|
||||||
|
return MagicColor.Color.COLORLESS;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getLocalisedMagicColorName(String colorName){
|
public static String getLocalisedMagicColorName(String colorName){
|
||||||
Localizer localizer = Localizer.getInstance();
|
Localizer localizer = Localizer.getInstance();
|
||||||
return switch (colorName.toLowerCase()) {
|
switch(colorName.toLowerCase()){
|
||||||
case MagicColor.Constant.WHITE -> localizer.getMessage("lblWhite");
|
case MagicColor.Constant.WHITE:
|
||||||
case MagicColor.Constant.BLUE -> localizer.getMessage("lblBlue");
|
return localizer.getMessage("lblWhite");
|
||||||
case MagicColor.Constant.BLACK -> localizer.getMessage("lblBlack");
|
|
||||||
case MagicColor.Constant.RED -> localizer.getMessage("lblRed");
|
case MagicColor.Constant.BLUE:
|
||||||
case MagicColor.Constant.GREEN -> localizer.getMessage("lblGreen");
|
return localizer.getMessage("lblBlue");
|
||||||
case MagicColor.Constant.COLORLESS -> localizer.getMessage("lblColorless");
|
|
||||||
case "multicolour", "multicolor" -> localizer.getMessage("lblMulticolor");
|
case MagicColor.Constant.BLACK:
|
||||||
default -> "";
|
return localizer.getMessage("lblBlack");
|
||||||
};
|
|
||||||
|
case MagicColor.Constant.RED:
|
||||||
|
return localizer.getMessage("lblRed");
|
||||||
|
|
||||||
|
case MagicColor.Constant.GREEN:
|
||||||
|
return localizer.getMessage("lblGreen");
|
||||||
|
|
||||||
|
case MagicColor.Constant.COLORLESS:
|
||||||
|
return localizer.getMessage("lblColorless");
|
||||||
|
case "multicolour":
|
||||||
|
case "multicolor":
|
||||||
|
return localizer.getMessage("lblMulticolor");
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1055,6 +1080,37 @@ public class DeckRecognizer {
|
|||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Pair<String, String> getManaNameAndSymbol(String matchedMana) {
|
||||||
|
if (matchedMana == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Localizer localizer = Localizer.getInstance();
|
||||||
|
switch (matchedMana.toLowerCase()) {
|
||||||
|
case MagicColor.Constant.WHITE:
|
||||||
|
case "w":
|
||||||
|
return Pair.of(localizer.getMessage("lblWhite"), MagicColor.Color.WHITE.getSymbol());
|
||||||
|
case MagicColor.Constant.BLUE:
|
||||||
|
case "u":
|
||||||
|
return Pair.of(localizer.getMessage("lblBlue"), MagicColor.Color.BLUE.getSymbol());
|
||||||
|
case MagicColor.Constant.BLACK:
|
||||||
|
case "b":
|
||||||
|
return Pair.of(localizer.getMessage("lblBlack"), MagicColor.Color.BLACK.getSymbol());
|
||||||
|
case MagicColor.Constant.RED:
|
||||||
|
case "r":
|
||||||
|
return Pair.of(localizer.getMessage("lblRed"), MagicColor.Color.RED.getSymbol());
|
||||||
|
case MagicColor.Constant.GREEN:
|
||||||
|
case "g":
|
||||||
|
return Pair.of(localizer.getMessage("lblGreen"), MagicColor.Color.GREEN.getSymbol());
|
||||||
|
case MagicColor.Constant.COLORLESS:
|
||||||
|
case "c":
|
||||||
|
return Pair.of(localizer.getMessage("lblColorless"), MagicColor.Color.COLORLESS.getSymbol());
|
||||||
|
default: // Multicolour
|
||||||
|
return Pair.of(localizer.getMessage("lblMulticolor"), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isDeckName(final String lineAsIs) {
|
public static boolean isDeckName(final String lineAsIs) {
|
||||||
if (lineAsIs == null)
|
if (lineAsIs == null)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -52,4 +52,9 @@ public interface IPaperCard extends InventoryItem, Serializable {
|
|||||||
default String getUntranslatedType() {
|
default String getUntranslatedType() {
|
||||||
return getRules().getType().toString();
|
return getRules().getType().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getUntranslatedOracle() {
|
||||||
|
return getRules().getOracleText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
||||||
this.foil = foil;
|
this.foil = foil;
|
||||||
this.rarity = rarity;
|
this.rarity = rarity;
|
||||||
this.artist = artist;
|
this.artist = TextUtil.normalizeText(artist);
|
||||||
this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER;
|
this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER;
|
||||||
// If the user changes the language this will make cards sort by the old language until they restart the game.
|
// If the user changes the language this will make cards sort by the old language until they restart the game.
|
||||||
// This is a good tradeoff
|
// This is a good tradeoff
|
||||||
@@ -375,8 +375,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
|
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
|
||||||
pc = readObjectAlternate(name, edition);
|
pc = readObjectAlternate(name, edition);
|
||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
pc = StaticData.instance().getCommonCards().createUnsupportedCard(name);
|
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
||||||
//throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
|
||||||
}
|
}
|
||||||
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
|
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
|
||||||
}
|
}
|
||||||
@@ -593,7 +592,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
|
|
||||||
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
||||||
if(markedColors == null)
|
if(markedColors == null)
|
||||||
markedColors = ColorSet.NO_COLORS;
|
markedColors = ColorSet.getNullColor();
|
||||||
return new PaperCardFlags(this, markedColors, null);
|
return new PaperCardFlags(this, markedColors, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
return false;
|
return false;
|
||||||
CardSplitType cst = this.cardRules.getSplitType();
|
CardSplitType cst = this.cardRules.getSplitType();
|
||||||
//expand this on future for other tokens that has other backsides besides transform..
|
//expand this on future for other tokens that has other backsides besides transform..
|
||||||
return cst == CardSplitType.Transform || cst == CardSplitType.Modal;
|
return cst == CardSplitType.Transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -633,10 +633,7 @@ public class BoosterGenerator {
|
|||||||
System.out.println("Parsing from main code: " + mainCode);
|
System.out.println("Parsing from main code: " + mainCode);
|
||||||
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
||||||
System.out.println("Attempting to lookup: " + sheetName);
|
System.out.println("Attempting to lookup: " + sheetName);
|
||||||
PrintSheet fromSheet = tryGetStaticSheet(sheetName);
|
src = tryGetStaticSheet(sheetName).toFlatList();
|
||||||
if (fromSheet == null)
|
|
||||||
throw new RuntimeException("PrintSheet Error: " + ps.getName() + " didn't find " + sheetName + " from " + mainCode);
|
|
||||||
src = fromSheet.toFlatList();
|
|
||||||
setPred = x -> true;
|
setPred = x -> true;
|
||||||
|
|
||||||
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet
|
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ public interface ITranslatable extends IHasName {
|
|||||||
default String getUntranslatedName() {
|
default String getUntranslatedName() {
|
||||||
return getName();
|
return getName();
|
||||||
}
|
}
|
||||||
default String getTranslatedName() {
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
default String getUntranslatedType() {
|
default String getUntranslatedType() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default String getUntranslatedOracle() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,6 +207,8 @@ public class ImageUtil {
|
|||||||
else
|
else
|
||||||
editionCode = cp.getEdition().toLowerCase();
|
editionCode = cp.getEdition().toLowerCase();
|
||||||
String cardCollectorNumber = cp.getCollectorNumber();
|
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
|
// override old planechase sets from their modified id since scryfall move the planechase cards outside their original setcode
|
||||||
if (cardCollectorNumber.startsWith("OHOP")) {
|
if (cardCollectorNumber.startsWith("OHOP")) {
|
||||||
editionCode = "ohop";
|
editionCode = "ohop";
|
||||||
@@ -250,11 +252,6 @@ public class ImageUtil {
|
|||||||
: "&face=front");
|
: "&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),
|
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, encodeUtf8(cardCollectorNumber),
|
||||||
langCode, versionParam, faceParam);
|
langCode, versionParam, faceParam);
|
||||||
}
|
}
|
||||||
@@ -264,10 +261,6 @@ public class ImageUtil {
|
|||||||
if (!faceParam.isEmpty()) {
|
if (!faceParam.isEmpty()) {
|
||||||
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
|
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),
|
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, encodeUtf8(collectorNumber),
|
||||||
langCode, versionParam, faceParam);
|
langCode, versionParam, faceParam);
|
||||||
}
|
}
|
||||||
@@ -288,7 +281,8 @@ public class ImageUtil {
|
|||||||
char c;
|
char c;
|
||||||
for (int i = 0; i < in.length(); i++) {
|
for (int i = 0; i < in.length(); i++) {
|
||||||
c = in.charAt(i);
|
c = in.charAt(i);
|
||||||
if ((c != '"') && (c != '/') && (c != ':') && (c != '?')) {
|
if ((c == '"') || (c == '/') || (c == ':') || (c == '?')) {
|
||||||
|
} else {
|
||||||
out.append(c);
|
out.append(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.sentry</groupId>
|
<groupId>io.sentry</groupId>
|
||||||
<artifactId>sentry-logback</artifactId>
|
<artifactId>sentry-logback</artifactId>
|
||||||
<version>8.21.1</version>
|
<version>8.19.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jgrapht</groupId>
|
<groupId>org.jgrapht</groupId>
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
|
|
||||||
/** Keys of descriptive (text) parameters. */
|
/** Keys of descriptive (text) parameters. */
|
||||||
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
||||||
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription")
|
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription").build();
|
||||||
.add("ChangeTypeDesc")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys that should not changed
|
* Keys that should not changed
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class ForgeScript {
|
|||||||
boolean withSource = property.endsWith("Source");
|
boolean withSource = property.endsWith("Source");
|
||||||
final ColorSet colors;
|
final ColorSet colors;
|
||||||
if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) {
|
if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) {
|
||||||
colors = ColorSet.NO_COLORS;
|
colors = ColorSet.getNullColor();
|
||||||
} else {
|
} else {
|
||||||
colors = cardState.getCard().getColor(cardState);
|
colors = cardState.getCard().getColor(cardState);
|
||||||
}
|
}
|
||||||
@@ -166,6 +166,8 @@ public class ForgeScript {
|
|||||||
Card source, CardTraitBase spellAbility) {
|
Card source, CardTraitBase spellAbility) {
|
||||||
if (property.equals("ManaAbility")) {
|
if (property.equals("ManaAbility")) {
|
||||||
return sa.isManaAbility();
|
return sa.isManaAbility();
|
||||||
|
} else if (property.equals("nonManaAbility")) {
|
||||||
|
return !sa.isManaAbility();
|
||||||
} else if (property.equals("withoutXCost")) {
|
} else if (property.equals("withoutXCost")) {
|
||||||
return !sa.costHasManaX();
|
return !sa.costHasManaX();
|
||||||
} else if (property.startsWith("XCost")) {
|
} else if (property.startsWith("XCost")) {
|
||||||
@@ -193,7 +195,7 @@ public class ForgeScript {
|
|||||||
return sa.isKeyword(Keyword.SADDLE);
|
return sa.isKeyword(Keyword.SADDLE);
|
||||||
} else if (property.equals("Station")) {
|
} else if (property.equals("Station")) {
|
||||||
return sa.isKeyword(Keyword.STATION);
|
return sa.isKeyword(Keyword.STATION);
|
||||||
} else if (property.equals("Cycling")) {
|
}else if (property.equals("Cycling")) {
|
||||||
return sa.isCycling();
|
return sa.isCycling();
|
||||||
} else if (property.equals("Dash")) {
|
} else if (property.equals("Dash")) {
|
||||||
return sa.isDash();
|
return sa.isDash();
|
||||||
@@ -235,8 +237,6 @@ public class ForgeScript {
|
|||||||
return sa.isBoast();
|
return sa.isBoast();
|
||||||
} else if (property.equals("Exhaust")) {
|
} else if (property.equals("Exhaust")) {
|
||||||
return sa.isExhaust();
|
return sa.isExhaust();
|
||||||
} else if (property.equals("Mayhem")) {
|
|
||||||
return sa.isMayhem();
|
|
||||||
} else if (property.equals("Mutate")) {
|
} else if (property.equals("Mutate")) {
|
||||||
return sa.isMutate();
|
return sa.isMutate();
|
||||||
} else if (property.equals("Ninjutsu")) {
|
} else if (property.equals("Ninjutsu")) {
|
||||||
@@ -410,8 +410,6 @@ public class ForgeScript {
|
|||||||
return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed();
|
return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if(property.startsWith("NamedAbility")) {
|
|
||||||
return sa.getName().equals(property.substring(12));
|
|
||||||
} else if (sa.getHostCard() != null) {
|
} else if (sa.getHostCard() != null) {
|
||||||
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,6 +414,19 @@ public class Game {
|
|||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the nonactive players who are still fighting to win, in turn order.
|
||||||
|
*/
|
||||||
|
public final PlayerCollection getNonactivePlayers() {
|
||||||
|
// Don't use getPlayersInTurnOrder to prevent copying the player collection twice
|
||||||
|
final PlayerCollection players = new PlayerCollection(ingamePlayers);
|
||||||
|
players.remove(phaseHandler.getPlayerTurn());
|
||||||
|
if (!getTurnOrder().isDefaultDirection()) {
|
||||||
|
Collections.reverse(players);
|
||||||
|
}
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the players who participated in match (regardless of outcome).
|
* Gets the players who participated in match (regardless of outcome).
|
||||||
* <i>Use this in UI and after match calculations</i>
|
* <i>Use this in UI and after match calculations</i>
|
||||||
@@ -845,8 +858,6 @@ public class Game {
|
|||||||
p.revealFaceDownCards();
|
p.revealFaceDownCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO free any mindslaves
|
|
||||||
|
|
||||||
for (Card c : cards) {
|
for (Card c : cards) {
|
||||||
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
||||||
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
||||||
@@ -882,6 +893,8 @@ public class Game {
|
|||||||
}
|
}
|
||||||
triggerList.put(c.getZone().getZoneType(), null, c);
|
triggerList.put(c.getZone().getZoneType(), null, c);
|
||||||
getAction().ceaseToExist(c, false);
|
getAction().ceaseToExist(c, false);
|
||||||
|
// CR 603.2f owner of trigger source lost game
|
||||||
|
getTriggerHandler().clearDelayedTrigger(c);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// return stolen permanents
|
// return stolen permanents
|
||||||
|
|||||||
@@ -57,8 +57,6 @@ import forge.item.PaperCard;
|
|||||||
import forge.util.*;
|
import forge.util.*;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
import io.sentry.Breadcrumb;
|
|
||||||
import io.sentry.Sentry;
|
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.jgrapht.alg.cycle.SzwarcfiterLauerSimpleCycles;
|
import org.jgrapht.alg.cycle.SzwarcfiterLauerSimpleCycles;
|
||||||
import org.jgrapht.graph.DefaultDirectedGraph;
|
import org.jgrapht.graph.DefaultDirectedGraph;
|
||||||
@@ -222,6 +220,10 @@ public class GameAction {
|
|||||||
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
|
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c.isTransformed()) {
|
||||||
|
copied.incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
||||||
copied.setCastSA(cause);
|
copied.setCastSA(cause);
|
||||||
copied.setSplitStateToPlayAbility(cause);
|
copied.setSplitStateToPlayAbility(cause);
|
||||||
@@ -751,29 +753,26 @@ public class GameAction {
|
|||||||
|
|
||||||
public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map<AbilityKey, Object> params) {
|
public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map<AbilityKey, Object> params) {
|
||||||
// Call specific functions to set PlayerZone, then move onto moveTo
|
// Call specific functions to set PlayerZone, then move onto moveTo
|
||||||
try {
|
switch(name) {
|
||||||
return switch (name) {
|
case Hand: return moveToHand(c, cause, params);
|
||||||
case Hand -> moveToHand(c, cause, params);
|
case Library: return moveToLibrary(c, libPosition, cause, params);
|
||||||
case Library -> moveToLibrary(c, libPosition, cause, params);
|
case Battlefield: return moveToPlay(c, c.getController(), cause, params);
|
||||||
case Battlefield -> moveToPlay(c, c.getController(), cause, params);
|
case Graveyard: return moveToGraveyard(c, cause, params);
|
||||||
case Graveyard -> moveToGraveyard(c, cause, params);
|
case Exile:
|
||||||
case Exile -> !c.canExiledBy(cause, true) ? null : exile(c, cause, params);
|
if (!c.canExiledBy(cause, true)) {
|
||||||
case Stack -> moveToStack(c, cause, params);
|
return null;
|
||||||
case PlanarDeck, SchemeDeck, AttractionDeck, ContraptionDeck -> moveToVariantDeck(c, name, libPosition, cause, params);
|
}
|
||||||
case Junkyard -> moveToJunkyard(c, cause, params);
|
return exile(c, cause, params);
|
||||||
default -> moveTo(c.getOwner().getZone(name), c, cause); // sideboard will also get there
|
case Stack: return moveToStack(c, cause, params);
|
||||||
};
|
case PlanarDeck:
|
||||||
} catch (Exception e) {
|
case SchemeDeck:
|
||||||
String msg = "GameAction:moveTo: Exception occured";
|
case AttractionDeck:
|
||||||
|
case ContraptionDeck:
|
||||||
Breadcrumb bread = new Breadcrumb(msg);
|
return moveToVariantDeck(c, name, libPosition, cause, params);
|
||||||
bread.setData("Card", c.getName());
|
case Junkyard:
|
||||||
bread.setData("SA", cause.toString());
|
return moveToJunkyard(c, cause, params);
|
||||||
bread.setData("ZoneType", name.name());
|
default: // sideboard will also get there
|
||||||
bread.setData("Player", c.getOwner());
|
return moveTo(c.getOwner().getZone(name), c, cause);
|
||||||
Sentry.addBreadcrumb(bread);
|
|
||||||
|
|
||||||
throw new RuntimeException("Error in GameAction moveTo " + c.getName() + " to Player Zone " + name.name(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,7 +974,6 @@ public class GameAction {
|
|||||||
// in some corner cases there's no zone yet (copied spell that failed targeting)
|
// in some corner cases there's no zone yet (copied spell that failed targeting)
|
||||||
if (z != null) {
|
if (z != null) {
|
||||||
z.remove(c);
|
z.remove(c);
|
||||||
c.setZone(c.getOwner().getZone(ZoneType.None));
|
|
||||||
if (z.is(ZoneType.Battlefield)) {
|
if (z.is(ZoneType.Battlefield)) {
|
||||||
c.runLeavesPlayCommands();
|
c.runLeavesPlayCommands();
|
||||||
}
|
}
|
||||||
@@ -1602,7 +1600,9 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// recheck the game over condition at this point to make sure no other win conditions apply now.
|
// recheck the game over condition at this point to make sure no other win conditions apply now.
|
||||||
checkGameOverCondition();
|
if (!game.isGameOver()) {
|
||||||
|
checkGameOverCondition();
|
||||||
|
}
|
||||||
|
|
||||||
if (game.getAge() != GameStage.Play) {
|
if (game.getAge() != GameStage.Play) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1822,8 +1822,8 @@ public class GameAction {
|
|||||||
|
|
||||||
private boolean stateBasedAction704_5q(Card c) {
|
private boolean stateBasedAction704_5q(Card c) {
|
||||||
boolean checkAgain = false;
|
boolean checkAgain = false;
|
||||||
final CounterType p1p1 = CounterEnumType.P1P1;
|
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||||
final CounterType m1m1 = CounterEnumType.M1M1;
|
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||||
int plusOneCounters = c.getCounters(p1p1);
|
int plusOneCounters = c.getCounters(p1p1);
|
||||||
int minusOneCounters = c.getCounters(m1m1);
|
int minusOneCounters = c.getCounters(m1m1);
|
||||||
if (plusOneCounters > 0 && minusOneCounters > 0) {
|
if (plusOneCounters > 0 && minusOneCounters > 0) {
|
||||||
@@ -1843,7 +1843,7 @@ public class GameAction {
|
|||||||
return checkAgain;
|
return checkAgain;
|
||||||
}
|
}
|
||||||
private boolean stateBasedAction704_5r(Card c) {
|
private boolean stateBasedAction704_5r(Card c) {
|
||||||
final CounterType dreamType = CounterEnumType.DREAM;
|
final CounterType dreamType = CounterType.get(CounterEnumType.DREAM);
|
||||||
|
|
||||||
int old = c.getCounters(dreamType);
|
int old = c.getCounters(dreamType);
|
||||||
if (old <= 0) {
|
if (old <= 0) {
|
||||||
@@ -1883,10 +1883,6 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void checkGameOverCondition() {
|
public void checkGameOverCondition() {
|
||||||
if (game.isGameOver()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// award loses as SBE
|
// award loses as SBE
|
||||||
GameEndReason reason = null;
|
GameEndReason reason = null;
|
||||||
List<Player> losers = null;
|
List<Player> losers = null;
|
||||||
@@ -2226,13 +2222,6 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
|
|
||||||
// Notify players
|
|
||||||
for (Player p : game.getPlayers()) {
|
|
||||||
p.getController().revealUnsupported(unsupported);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Delivers a message to all players. (use reveal to show Cards) */
|
/** Delivers a message to all players. (use reveal to show Cards) */
|
||||||
public void notifyOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) {
|
public void notifyOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) {
|
||||||
if (saSource != null) {
|
if (saSource != null) {
|
||||||
|
|||||||
@@ -125,22 +125,10 @@ public final class GameActionUtil {
|
|||||||
|
|
||||||
// need to be done there before static abilities does reset the card
|
// need to be done there before static abilities does reset the card
|
||||||
// These Keywords depend on the Mana Cost of for Split Cards
|
// These Keywords depend on the Mana Cost of for Split Cards
|
||||||
if (sa.isBasicSpell()) {
|
if (sa.isBasicSpell() && !sa.isLandAbility()) {
|
||||||
for (final KeywordInterface inst : source.getKeywords()) {
|
for (final KeywordInterface inst : source.getKeywords()) {
|
||||||
final String keyword = inst.getOriginal();
|
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 (keyword.startsWith("Escape")) {
|
||||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -178,6 +166,18 @@ public final class GameActionUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Flashback));
|
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")) {
|
} else if (keyword.startsWith("Harmonize")) {
|
||||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -242,7 +242,6 @@ public final class GameActionUtil {
|
|||||||
}
|
}
|
||||||
stackCopy.setLastKnownZone(game.getStackZone());
|
stackCopy.setLastKnownZone(game.getStackZone());
|
||||||
stackCopy.setCastFrom(oldZone);
|
stackCopy.setCastFrom(oldZone);
|
||||||
stackCopy.setCastSA(sa);
|
|
||||||
lkicheck = true;
|
lkicheck = true;
|
||||||
|
|
||||||
stackCopy.clearStaticChangedCardKeywords(false);
|
stackCopy.clearStaticChangedCardKeywords(false);
|
||||||
@@ -993,6 +992,9 @@ public final class GameActionUtil {
|
|||||||
oldCard.setBackSide(false);
|
oldCard.setBackSide(false);
|
||||||
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
||||||
oldCard.unanimateBestow();
|
oldCard.unanimateBestow();
|
||||||
|
if (ability.isDisturb() || ability.hasParam("CastTransformed")) {
|
||||||
|
oldCard.undoIncrementTransformedTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
if (ability.hasParam("Prototype")) {
|
if (ability.hasParam("Prototype")) {
|
||||||
oldCard.removeCloneState(oldCard.getPrototypeTimestamp());
|
oldCard.removeCloneState(oldCard.getPrototypeTimestamp());
|
||||||
|
|||||||
@@ -33,18 +33,17 @@ import forge.game.card.CardCollection;
|
|||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.event.GameEventCardAttachment;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.keyword.KeywordWithType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
|
||||||
import forge.game.staticability.StaticAbilityCantAttach;
|
import forge.game.staticability.StaticAbilityCantAttach;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Lang;
|
|
||||||
|
|
||||||
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||||
protected int id;
|
protected int id;
|
||||||
@@ -198,12 +197,14 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
public final void addAttachedCard(final Card c) {
|
public final void addAttachedCard(final Card c) {
|
||||||
if (attachedCards.add(c)) {
|
if (attachedCards.add(c)) {
|
||||||
updateAttachedCards();
|
updateAttachedCards();
|
||||||
|
getGame().fireEvent(new GameEventCardAttachment(c, null, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void removeAttachedCard(final Card c) {
|
public final void removeAttachedCard(final Card c) {
|
||||||
if (attachedCards.remove(c)) {
|
if (attachedCards.remove(c)) {
|
||||||
updateAttachedCards();
|
updateAttachedCards();
|
||||||
|
getGame().fireEvent(new GameEventCardAttachment(c, this, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,83 +222,63 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
return canBeAttached(attach, sa, false);
|
return canBeAttached(attach, sa, false);
|
||||||
}
|
}
|
||||||
public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) {
|
public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) {
|
||||||
return cantBeAttachedMsg(attach, sa, checkSBA) == null;
|
// master mode
|
||||||
}
|
if (!attach.isAttachment() || (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE))
|
||||||
|
|| equals(attach)) {
|
||||||
public String cantBeAttachedMsg(final Card attach, SpellAbility sa) {
|
return false;
|
||||||
return cantBeAttachedMsg(attach, sa, false);
|
|
||||||
}
|
|
||||||
public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) {
|
|
||||||
if (!attach.isAttachment()) {
|
|
||||||
return attach.getName() + " is not an attachment";
|
|
||||||
}
|
|
||||||
if (equals(attach)) {
|
|
||||||
return attach.getName() + " can't attach to itself";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE)) {
|
|
||||||
return attach.getName() + " is a creature without reconfigure";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attach.isPhasedOut()) {
|
if (attach.isPhasedOut()) {
|
||||||
return attach.getName() + " is phased out";
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attach.isAura()) {
|
// check for rules
|
||||||
String msg = cantBeEnchantedByMsg(attach);
|
if (attach.isAura() && !canBeEnchantedBy(attach)) {
|
||||||
if (msg != null) {
|
return false;
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (attach.isEquipment()) {
|
if (attach.isEquipment() && !canBeEquippedBy(attach, sa)) {
|
||||||
String msg = cantBeEquippedByMsg(attach, sa);
|
return false;
|
||||||
if (msg != null) {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (attach.isFortification()) {
|
if (attach.isFortification() && !canBeFortifiedBy(attach)) {
|
||||||
String msg = cantBeFortifiedByMsg(attach);
|
return false;
|
||||||
if (msg != null) {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StaticAbility stAb = StaticAbilityCantAttach.cantAttach(this, attach, checkSBA);
|
// check for can't attach static
|
||||||
if (stAb != null) {
|
if (StaticAbilityCantAttach.cantAttach(this, attach, checkSBA)) {
|
||||||
return stAb.toString();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// true for all
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String cantBeEquippedByMsg(final Card aura, SpellAbility sa) {
|
protected boolean canBeEquippedBy(final Card aura, SpellAbility sa) {
|
||||||
|
/**
|
||||||
|
* Equip only to Creatures which are cards
|
||||||
|
*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canBeFortifiedBy(final Card aura) {
|
||||||
/**
|
/**
|
||||||
* Equip only to Lands which are cards
|
* Equip only to Lands which are cards
|
||||||
*/
|
*/
|
||||||
return getName() + " is not a Creature";
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String cantBeFortifiedByMsg(final Card fort) {
|
protected boolean canBeEnchantedBy(final Card aura) {
|
||||||
/**
|
|
||||||
* Equip only to Lands which are cards
|
|
||||||
*/
|
|
||||||
return getName() + " is not a Land";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String cantBeEnchantedByMsg(final Card aura) {
|
|
||||||
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
||||||
return "No Enchant Keyword";
|
return false;
|
||||||
}
|
}
|
||||||
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||||
if (ki instanceof KeywordWithType kwt) {
|
String k = ki.getOriginal();
|
||||||
String v = kwt.getValidType();
|
String m[] = k.split(":");
|
||||||
String desc = kwt.getTypeDescription();
|
String v = m[1];
|
||||||
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||||
return getName() + " is not " + Lang.nounWithAmount(1, desc);
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCounters() {
|
public boolean hasCounters() {
|
||||||
@@ -324,6 +305,9 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
Integer value = counters.get(counterName);
|
Integer value = counters.get(counterName);
|
||||||
return value == null ? 0 : value;
|
return value == null ? 0 : value;
|
||||||
}
|
}
|
||||||
|
public final int getCounters(final CounterEnumType counterType) {
|
||||||
|
return getCounters(CounterType.get(counterType));
|
||||||
|
}
|
||||||
|
|
||||||
public void setCounters(final CounterType counterType, final Integer num) {
|
public void setCounters(final CounterType counterType, final Integer num) {
|
||||||
if (num <= 0) {
|
if (num <= 0) {
|
||||||
@@ -332,6 +316,9 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
counters.put(counterType, num);
|
counters.put(counterType, num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void setCounters(final CounterEnumType counterType, final Integer num) {
|
||||||
|
setCounters(CounterType.get(counterType), num);
|
||||||
|
}
|
||||||
|
|
||||||
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
|
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
|
||||||
|
|
||||||
@@ -341,6 +328,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
abstract public int subtractCounter(final CounterType counterName, final int n, final Player remover);
|
abstract public int subtractCounter(final CounterType counterName, final int n, final Player remover);
|
||||||
abstract public void clearCounters();
|
abstract public void clearCounters();
|
||||||
|
|
||||||
|
public boolean canReceiveCounters(final CounterEnumType type) {
|
||||||
|
return canReceiveCounters(CounterType.get(type));
|
||||||
|
}
|
||||||
|
|
||||||
public final void addCounter(final CounterType counterType, int n, final Player source, GameEntityCounterTable table) {
|
public final void addCounter(final CounterType counterType, int n, final Player source, GameEntityCounterTable table) {
|
||||||
if (n <= 0 || !canReceiveCounters(counterType)) {
|
if (n <= 0 || !canReceiveCounters(counterType)) {
|
||||||
// As per rule 107.1b
|
// As per rule 107.1b
|
||||||
@@ -360,7 +351,18 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
table.put(source, this, counterType, n);
|
table.put(source, this, counterType, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void addCounter(final CounterEnumType counterType, final int n, final Player source, GameEntityCounterTable table) {
|
||||||
|
addCounter(CounterType.get(counterType), n, source, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int subtractCounter(final CounterEnumType counterName, final int n, final Player remover) {
|
||||||
|
return subtractCounter(CounterType.get(counterName), n, remover);
|
||||||
|
}
|
||||||
|
|
||||||
abstract public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params);
|
abstract public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params);
|
||||||
|
public void addCounterInternal(final CounterEnumType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params) {
|
||||||
|
addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table, params);
|
||||||
|
}
|
||||||
public Integer getCounterMax(final CounterType counterType) {
|
public Integer getCounterMax(final CounterType counterType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,25 +29,25 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventGameOutcome ev) {
|
public GameLogEntry visit(GameEventGameOutcome ev) {
|
||||||
// Turn number counted from the starting player
|
// Turn number counted from the starting player
|
||||||
int lastTurn = (int)Math.ceil((float)ev.result().getLastTurnNumber() / 2.0);
|
int lastTurn = (int)Math.ceil((float)ev.result.getLastTurnNumber() / 2.0);
|
||||||
log.add(GameLogEntryType.GAME_OUTCOME, localizer.getMessage("lblTurn") + " " + lastTurn);
|
log.add(GameLogEntryType.GAME_OUTCOME, localizer.getMessage("lblTurn") + " " + lastTurn);
|
||||||
|
|
||||||
for (String outcome : ev.result().getOutcomeStrings()) {
|
for (String outcome : ev.result.getOutcomeStrings()) {
|
||||||
log.add(GameLogEntryType.GAME_OUTCOME, outcome);
|
log.add(GameLogEntryType.GAME_OUTCOME, outcome);
|
||||||
}
|
}
|
||||||
return generateSummary(ev.history());
|
return generateSummary(ev.history);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventScry ev) {
|
public GameLogEntry visit(GameEventScry ev) {
|
||||||
String scryOutcome = "";
|
String scryOutcome = "";
|
||||||
|
|
||||||
if (ev.toTop() > 0 && ev.toBottom() > 0) {
|
if (ev.toTop > 0 && ev.toBottom > 0) {
|
||||||
scryOutcome = localizer.getMessage("lblLogScryTopBottomLibrary").replace("%s", ev.player().toString()).replace("%top", String.valueOf(ev.toTop())).replace("%bottom", String.valueOf(ev.toBottom()));
|
scryOutcome = localizer.getMessage("lblLogScryTopBottomLibrary").replace("%s", ev.player.toString()).replace("%top", String.valueOf(ev.toTop)).replace("%bottom", String.valueOf(ev.toBottom));
|
||||||
} else if (ev.toBottom() == 0) {
|
} else if (ev.toBottom == 0) {
|
||||||
scryOutcome = localizer.getMessage("lblLogScryTopLibrary").replace("%s", ev.player().toString()).replace("%top", String.valueOf(ev.toTop()));
|
scryOutcome = localizer.getMessage("lblLogScryTopLibrary").replace("%s", ev.player.toString()).replace("%top", String.valueOf(ev.toTop));
|
||||||
} else {
|
} else {
|
||||||
scryOutcome = localizer.getMessage("lblLogScryBottomLibrary").replace("%s", ev.player().toString()).replace("%bottom", String.valueOf(ev.toBottom()));
|
scryOutcome = localizer.getMessage("lblLogScryBottomLibrary").replace("%s", ev.player.toString()).replace("%bottom", String.valueOf(ev.toBottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, scryOutcome);
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, scryOutcome);
|
||||||
@@ -57,12 +57,12 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
public GameLogEntry visit(GameEventSurveil ev) {
|
public GameLogEntry visit(GameEventSurveil ev) {
|
||||||
String surveilOutcome = "";
|
String surveilOutcome = "";
|
||||||
|
|
||||||
if (ev.toLibrary() > 0 && ev.toGraveyard() > 0) {
|
if (ev.toLibrary > 0 && ev.toGraveyard > 0) {
|
||||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibraryGraveyard", ev.player().toString(), String.valueOf(ev.toLibrary()), String.valueOf(ev.toGraveyard()));
|
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibraryGraveyard", ev.player.toString(), String.valueOf(ev.toLibrary), String.valueOf(ev.toGraveyard));
|
||||||
} else if (ev.toGraveyard() == 0) {
|
} else if (ev.toGraveyard == 0) {
|
||||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibrary", ev.player().toString(), String.valueOf(ev.toLibrary()));
|
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibrary", ev.player.toString(), String.valueOf(ev.toLibrary));
|
||||||
} else {
|
} else {
|
||||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToGraveyard", ev.player().toString(), String.valueOf(ev.toGraveyard()));
|
surveilOutcome = localizer.getMessage("lblLogSurveiledToGraveyard", ev.player.toString(), String.valueOf(ev.toGraveyard));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, surveilOutcome);
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, surveilOutcome);
|
||||||
@@ -70,26 +70,26 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventSpellResolved ev) {
|
public GameLogEntry visit(GameEventSpellResolved ev) {
|
||||||
String messageForLog = ev.hasFizzled() ? localizer.getMessage("lblLogCardAbilityFizzles", ev.spell().getHostCard().toString()) : ev.spell().getStackDescription();
|
String messageForLog = ev.hasFizzled ? localizer.getMessage("lblLogCardAbilityFizzles", ev.spell.getHostCard().toString()) : ev.spell.getStackDescription();
|
||||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, messageForLog);
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, messageForLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventSpellAbilityCast event) {
|
public GameLogEntry visit(GameEventSpellAbilityCast event) {
|
||||||
String player = event.sa().getActivatingPlayer().getName();
|
String player = event.sa.getActivatingPlayer().getName();
|
||||||
String action = event.sa().isSpell() ? localizer.getMessage("lblCast")
|
String action = event.sa.isSpell() ? localizer.getMessage("lblCast")
|
||||||
: event.sa().isTrigger() ? localizer.getMessage("lblTriggered")
|
: event.sa.isTrigger() ? localizer.getMessage("lblTriggered")
|
||||||
: localizer.getMessage("lblActivated");
|
: localizer.getMessage("lblActivated");
|
||||||
String object = event.si().getStackDescription().startsWith("Morph ")
|
String object = event.si.getStackDescription().startsWith("Morph ")
|
||||||
? localizer.getMessage("lblMorph")
|
? localizer.getMessage("lblMorph")
|
||||||
: event.sa().getHostCard().toString();
|
: event.sa.getHostCard().toString();
|
||||||
|
|
||||||
String messageForLog = "";
|
String messageForLog = "";
|
||||||
|
|
||||||
if (event.sa().getTargetRestrictions() != null) {
|
if (event.sa.getTargetRestrictions() != null) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
for (TargetChoices ch : event.sa().getAllTargetChoices()) {
|
for (TargetChoices ch : event.sa.getAllTargetChoices()) {
|
||||||
if (null != ch) {
|
if (null != ch) {
|
||||||
sb.append(ch);
|
sb.append(ch);
|
||||||
}
|
}
|
||||||
@@ -104,18 +104,18 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventCardModeChosen ev) {
|
public GameLogEntry visit(GameEventCardModeChosen ev) {
|
||||||
if (!ev.log()) {
|
if (!ev.log) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String modeChoiceOutcome;
|
String modeChoiceOutcome;
|
||||||
if (ev.random()) {
|
if (ev.random) {
|
||||||
modeChoiceOutcome = localizer.getMessage("lblLogRandomMode", ev.cardName(), ev.mode());
|
modeChoiceOutcome = localizer.getMessage("lblLogRandomMode", ev.cardName, ev.mode);
|
||||||
} else {
|
} else {
|
||||||
modeChoiceOutcome = localizer.getMessage("lblLogPlayerChosenModeForCard",
|
modeChoiceOutcome = localizer.getMessage("lblLogPlayerChosenModeForCard",
|
||||||
ev.player().toString(), ev.mode(), ev.cardName());
|
ev.player.toString(), ev.mode, ev.cardName);
|
||||||
}
|
}
|
||||||
String name = CardTranslation.getTranslatedName(ev.cardName());
|
String name = CardTranslation.getTranslatedName(ev.cardName);
|
||||||
modeChoiceOutcome = TextUtil.fastReplace(modeChoiceOutcome, "CARDNAME", name);
|
modeChoiceOutcome = TextUtil.fastReplace(modeChoiceOutcome, "CARDNAME", name);
|
||||||
modeChoiceOutcome = TextUtil.fastReplace(modeChoiceOutcome, "NICKNAME",
|
modeChoiceOutcome = TextUtil.fastReplace(modeChoiceOutcome, "NICKNAME",
|
||||||
Lang.getInstance().getNickName(name));
|
Lang.getInstance().getNickName(name));
|
||||||
@@ -124,7 +124,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventRandomLog ev) {
|
public GameLogEntry visit(GameEventRandomLog ev) {
|
||||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, ev.message());
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, ev.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameLogEntry generateSummary(final Collection<GameOutcome> gamesPlayed) {
|
private static GameLogEntry generateSummary(final Collection<GameOutcome> gamesPlayed) {
|
||||||
@@ -152,8 +152,8 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(final GameEventPlayerControl event) {
|
public GameLogEntry visit(final GameEventPlayerControl event) {
|
||||||
final LobbyPlayer newLobbyPlayer = event.newLobbyPlayer();
|
final LobbyPlayer newLobbyPlayer = event.newLobbyPlayer;
|
||||||
final Player p = event.player();
|
final Player p = event.player;
|
||||||
|
|
||||||
final String message;
|
final String message;
|
||||||
if (newLobbyPlayer == null) {
|
if (newLobbyPlayer == null) {
|
||||||
@@ -166,23 +166,23 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventTurnPhase ev) {
|
public GameLogEntry visit(GameEventTurnPhase ev) {
|
||||||
Player p = ev.playerTurn();
|
Player p = ev.playerTurn;
|
||||||
return new GameLogEntry(GameLogEntryType.PHASE, ev.phaseDesc() + Lang.getInstance().getPossessedObject(p.getName(), ev.phase().nameForUi));
|
return new GameLogEntry(GameLogEntryType.PHASE, ev.phaseDesc + Lang.getInstance().getPossessedObject(p.getName(), ev.phase.nameForUi));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventCardDamaged event) {
|
public GameLogEntry visit(GameEventCardDamaged event) {
|
||||||
String additionalLog = "";
|
String additionalLog = "";
|
||||||
if (event.type() == DamageType.Deathtouch) {
|
if (event.type == DamageType.Deathtouch) {
|
||||||
additionalLog = localizer.getMessage("lblDeathtouch");
|
additionalLog = localizer.getMessage("lblDeathtouch");
|
||||||
}
|
}
|
||||||
if (event.type() == DamageType.M1M1Counters) {
|
if (event.type == DamageType.M1M1Counters) {
|
||||||
additionalLog = localizer.getMessage("lblAsM1M1Counters");
|
additionalLog = localizer.getMessage("lblAsM1M1Counters");
|
||||||
}
|
}
|
||||||
if (event.type() == DamageType.LoyaltyLoss) {
|
if (event.type == DamageType.LoyaltyLoss) {
|
||||||
additionalLog = localizer.getMessage("lblRemovingNLoyaltyCounter", String.valueOf(event.amount()));
|
additionalLog = localizer.getMessage("lblRemovingNLoyaltyCounter", String.valueOf(event.amount));
|
||||||
}
|
}
|
||||||
String message = localizer.getMessage("lblSourceDealsNDamageToDest", event.source().toString(), String.valueOf(event.amount()), additionalLog, event.card().toString());
|
String message = localizer.getMessage("lblSourceDealsNDamageToDest", event.source.toString(), String.valueOf(event.amount), additionalLog, event.card.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,43 +191,43 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventLandPlayed ev) {
|
public GameLogEntry visit(GameEventLandPlayed ev) {
|
||||||
String message = localizer.getMessage("lblLogPlayerPlayedLand", ev.player().toString(), ev.land().toString());
|
String message = localizer.getMessage("lblLogPlayerPlayedLand", ev.player.toString(), ev.land.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.LAND, message);
|
return new GameLogEntry(GameLogEntryType.LAND, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventTurnBegan event) {
|
public GameLogEntry visit(GameEventTurnBegan event) {
|
||||||
String message = localizer.getMessage("lblLogTurnNOwnerByPlayer", String.valueOf(event.turnNumber()), event.turnOwner().toString());
|
String message = localizer.getMessage("lblLogTurnNOwnerByPlayer", String.valueOf(event.turnNumber), event.turnOwner.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.TURN, message);
|
return new GameLogEntry(GameLogEntryType.TURN, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventPlayerDamaged ev) {
|
public GameLogEntry visit(GameEventPlayerDamaged ev) {
|
||||||
String extra = ev.infect() ? localizer.getMessage("lblLogAsPoisonCounters") : "";
|
String extra = ev.infect ? localizer.getMessage("lblLogAsPoisonCounters") : "";
|
||||||
String damageType = ev.combat() ? localizer.getMessage("lblCombat") : localizer.getMessage("lblNonCombat");
|
String damageType = ev.combat ? localizer.getMessage("lblCombat") : localizer.getMessage("lblNonCombat");
|
||||||
String message = localizer.getMessage("lblLogSourceDealsNDamageOfTypeToDest", ev.source().toString(),
|
String message = localizer.getMessage("lblLogSourceDealsNDamageOfTypeToDest", ev.source.toString(),
|
||||||
String.valueOf(ev.amount()), damageType, ev.target().toString(), extra);
|
String.valueOf(ev.amount), damageType, ev.target.toString(), extra);
|
||||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventPlayerPoisoned ev) {
|
public GameLogEntry visit(GameEventPlayerPoisoned ev) {
|
||||||
String message = localizer.getMessage("lblLogPlayerReceivesNPosionCounterFrom",
|
String message = localizer.getMessage("lblLogPlayerReceivesNPosionCounterFrom",
|
||||||
ev.receiver().toString(), String.valueOf(ev.amount()), ev.source().toString());
|
ev.receiver.toString(), String.valueOf(ev.amount), ev.source.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventPlayerRadiation ev) {
|
public GameLogEntry visit(GameEventPlayerRadiation ev) {
|
||||||
String message;
|
String message;
|
||||||
final int change = ev.change();
|
final int change = ev.change;
|
||||||
String radCtr = CounterEnumType.RAD.getName().toLowerCase() + " " +
|
String radCtr = CounterEnumType.RAD.getName().toLowerCase() + " " +
|
||||||
Localizer.getInstance().getMessage("lblCounter").toLowerCase();
|
Localizer.getInstance().getMessage("lblCounter").toLowerCase();
|
||||||
if (change >= 0) message = localizer.getMessage("lblLogPlayerRadiation",
|
if (change >= 0) message = localizer.getMessage("lblLogPlayerRadiation",
|
||||||
ev.receiver().toString(), Lang.nounWithNumeralExceptOne(String.valueOf(change), radCtr),
|
ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(change), radCtr),
|
||||||
ev.source().toString());
|
ev.source.toString());
|
||||||
else message = localizer.getMessage("lblLogPlayerRadRemove",
|
else message = localizer.getMessage("lblLogPlayerRadRemove",
|
||||||
ev.receiver().toString(), Lang.nounWithNumeralExceptOne(String.valueOf(Math.abs(change)), radCtr));
|
ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(Math.abs(change)), radCtr));
|
||||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,16 +239,16 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
// Append Defending Player/Planeswalker
|
// Append Defending Player/Planeswalker
|
||||||
|
|
||||||
// Not a big fan of the triple nested loop here
|
// Not a big fan of the triple nested loop here
|
||||||
for (GameEntity k : ev.attackersMap().keySet()) {
|
for (GameEntity k : ev.attackersMap.keySet()) {
|
||||||
Collection<Card> attackers = ev.attackersMap().get(k);
|
Collection<Card> attackers = ev.attackersMap.get(k);
|
||||||
if (attackers == null || attackers.isEmpty()) {
|
if (attackers == null || attackers.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (sb.length() > 0) sb.append("\n");
|
if (sb.length() > 0) sb.append("\n");
|
||||||
sb.append(localizer.getMessage("lblLogPlayerAssignedAttackerToAttackTarget", ev.player(), Lang.joinHomogenous(attackers), k));
|
sb.append(localizer.getMessage("lblLogPlayerAssignedAttackerToAttackTarget", ev.player, Lang.joinHomogenous(attackers), k));
|
||||||
}
|
}
|
||||||
if (sb.length() == 0) {
|
if (sb.length() == 0) {
|
||||||
sb.append(localizer.getMessage("lblPlayerDidntAttackThisTurn").replace("%s", ev.player().toString()));
|
sb.append(localizer.getMessage("lblPlayerDidntAttackThisTurn").replace("%s", ev.player.toString()));
|
||||||
}
|
}
|
||||||
return new GameLogEntry(GameLogEntryType.COMBAT, sb.toString());
|
return new GameLogEntry(GameLogEntryType.COMBAT, sb.toString());
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
Collection<Card> blockers = null;
|
Collection<Card> blockers = null;
|
||||||
|
|
||||||
for (Entry<GameEntity, MapOfLists<Card, Card>> kv : ev.blockers().entrySet()) {
|
for (Entry<GameEntity, MapOfLists<Card, Card>> kv : ev.blockers.entrySet()) {
|
||||||
GameEntity defender = kv.getKey();
|
GameEntity defender = kv.getKey();
|
||||||
MapOfLists<Card, Card> attackers = kv.getValue();
|
MapOfLists<Card, Card> attackers = kv.getValue();
|
||||||
if (attackers == null || attackers.isEmpty()) {
|
if (attackers == null || attackers.isEmpty()) {
|
||||||
@@ -298,7 +298,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventMulligan ev) {
|
public GameLogEntry visit(GameEventMulligan ev) {
|
||||||
String message = localizer.getMessage("lblPlayerHasMulliganedDownToNCards").replace("%d", String.valueOf(ev.player().getZone(ZoneType.Hand).size())).replace("%s", ev.player().toString());
|
String message = localizer.getMessage("lblPlayerHasMulliganedDownToNCards").replace("%d", String.valueOf(ev.player.getZone(ZoneType.Hand).size())).replace("%s", ev.player.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.MULLIGAN, message);
|
return new GameLogEntry(GameLogEntryType.MULLIGAN, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ public class GameRules {
|
|||||||
private boolean AISideboardingEnabled = false;
|
private boolean AISideboardingEnabled = false;
|
||||||
private boolean sideboardForAI = false;
|
private boolean sideboardForAI = false;
|
||||||
private final Set<GameType> appliedVariants = EnumSet.noneOf(GameType.class);
|
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
|
// it's a preference, not rule... but I could hardly find a better place for it
|
||||||
private boolean useGrayText;
|
private boolean useGrayText;
|
||||||
@@ -125,12 +124,4 @@ public class GameRules {
|
|||||||
public void setWarnAboutAICards(final boolean warnAboutAICards) {
|
public void setWarnAboutAICards(final boolean warnAboutAICards) {
|
||||||
this.warnAboutAICards = warnAboutAICards;
|
this.warnAboutAICards = warnAboutAICards;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSimTimeout() {
|
|
||||||
return this.simTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSimTimeout(final int duration) {
|
|
||||||
this.simTimeout = duration;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,6 @@ public class GameView extends TrackableObject {
|
|||||||
}
|
}
|
||||||
public void setDependencies(Table<StaticAbility, StaticAbility, Set<StaticAbilityLayer>> dependencies) {
|
public void setDependencies(Table<StaticAbility, StaticAbility, Set<StaticAbilityLayer>> dependencies) {
|
||||||
if (dependencies.isEmpty()) {
|
if (dependencies.isEmpty()) {
|
||||||
set(TrackableProperty.Dependencies, "");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import forge.item.PaperCard;
|
|||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -225,7 +224,6 @@ public class Match {
|
|||||||
// friendliness
|
// friendliness
|
||||||
Map<Player, Map<DeckSection, List<? extends PaperCard>>> rAICards = new HashMap<>();
|
Map<Player, Map<DeckSection, List<? extends PaperCard>>> rAICards = new HashMap<>();
|
||||||
Multimap<Player, PaperCard> removedAnteCards = ArrayListMultimap.create();
|
Multimap<Player, PaperCard> removedAnteCards = ArrayListMultimap.create();
|
||||||
Map<Player, List<PaperCard>> unsupported = new HashMap<>();
|
|
||||||
|
|
||||||
final FCollectionView<Player> players = game.getPlayers();
|
final FCollectionView<Player> players = game.getPlayers();
|
||||||
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
|
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
|
||||||
@@ -290,32 +288,22 @@ public class Match {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Deck toCheck = psc.getDeck();
|
Deck myDeck = psc.getDeck();
|
||||||
if (toCheck == null) {
|
player.setDraftNotes(myDeck.getDraftNotes());
|
||||||
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;
|
Set<PaperCard> myRemovedAnteCards = null;
|
||||||
if (!rules.useAnte()) {
|
if (!rules.useAnte()) {
|
||||||
myRemovedAnteCards = getRemovedAnteCards(myDeck.getLeft());
|
myRemovedAnteCards = getRemovedAnteCards(myDeck);
|
||||||
for (PaperCard cp: myRemovedAnteCards) {
|
for (PaperCard cp: myRemovedAnteCards) {
|
||||||
for (Entry<DeckSection, CardPool> ds : myDeck.getLeft()) {
|
for (Entry<DeckSection, CardPool> ds : myDeck) {
|
||||||
ds.getValue().removeAll(cp);
|
ds.getValue().removeAll(cp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preparePlayerZone(player, ZoneType.Library, myDeck.getLeft().getMain(), psc.useRandomFoil());
|
preparePlayerZone(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
|
||||||
if (myDeck.getLeft().has(DeckSection.Sideboard)) {
|
if (myDeck.has(DeckSection.Sideboard)) {
|
||||||
preparePlayerZone(player, ZoneType.Sideboard, myDeck.getLeft().get(DeckSection.Sideboard), psc.useRandomFoil());
|
preparePlayerZone(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
|
||||||
|
|
||||||
// Assign Companion
|
// Assign Companion
|
||||||
Card companion = player.assignCompanion(game, person);
|
Card companion = player.assignCompanion(game, person);
|
||||||
@@ -334,7 +322,7 @@ public class Match {
|
|||||||
player.shuffle(null);
|
player.shuffle(null);
|
||||||
|
|
||||||
if (isFirstGame) {
|
if (isFirstGame) {
|
||||||
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck.getLeft());
|
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
|
||||||
if (cardsComplained != null && !cardsComplained.isEmpty()) {
|
if (cardsComplained != null && !cardsComplained.isEmpty()) {
|
||||||
rAICards.put(player, cardsComplained);
|
rAICards.put(player, cardsComplained);
|
||||||
}
|
}
|
||||||
@@ -349,7 +337,6 @@ public class Match {
|
|||||||
if (myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty()) {
|
if (myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty()) {
|
||||||
removedAnteCards.putAll(player, myRemovedAnteCards);
|
removedAnteCards.putAll(player, myRemovedAnteCards);
|
||||||
}
|
}
|
||||||
unsupported.put(player, myDeck.getRight());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Localizer localizer = Localizer.getInstance();
|
final Localizer localizer = Localizer.getInstance();
|
||||||
@@ -360,10 +347,6 @@ public class Match {
|
|||||||
if (!removedAnteCards.isEmpty()) {
|
if (!removedAnteCards.isEmpty()) {
|
||||||
game.getAction().revealAnte(localizer.getMessage("lblAnteCardsRemoved"), removedAnteCards);
|
game.getAction().revealAnte(localizer.getMessage("lblAnteCardsRemoved"), removedAnteCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unsupported.isEmpty()) {
|
|
||||||
game.getAction().revealUnsupported(unsupported);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeAnte(Game lastGame) {
|
private void executeAnte(Game lastGame) {
|
||||||
|
|||||||
@@ -239,10 +239,6 @@ public final class AbilityFactory {
|
|||||||
spellAbility.putParam("PrecostDesc", "Exhaust — ");
|
spellAbility.putParam("PrecostDesc", "Exhaust — ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapParams.containsKey("Named")) {
|
|
||||||
spellAbility.setName(mapParams.get("Named"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// *********************************************
|
// *********************************************
|
||||||
// set universal properties of the SpellAbility
|
// set universal properties of the SpellAbility
|
||||||
|
|
||||||
@@ -363,6 +359,9 @@ public final class AbilityFactory {
|
|||||||
if (mapParams.containsKey("TargetUnique")) {
|
if (mapParams.containsKey("TargetUnique")) {
|
||||||
abTgt.setUniqueTargets(true);
|
abTgt.setUniqueTargets(true);
|
||||||
}
|
}
|
||||||
|
if (mapParams.containsKey("TargetsFromSingleZone")) {
|
||||||
|
abTgt.setSingleZone(true);
|
||||||
|
}
|
||||||
if (mapParams.containsKey("TargetsWithoutSameCreatureType")) {
|
if (mapParams.containsKey("TargetsWithoutSameCreatureType")) {
|
||||||
abTgt.setWithoutSameCreatureType(true);
|
abTgt.setWithoutSameCreatureType(true);
|
||||||
}
|
}
|
||||||
@@ -384,9 +383,6 @@ public final class AbilityFactory {
|
|||||||
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
|
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
|
||||||
abTgt.setDifferentCMC(true);
|
abTgt.setDifferentCMC(true);
|
||||||
}
|
}
|
||||||
if (mapParams.containsKey("TargetsWithDifferentNames")) {
|
|
||||||
abTgt.setDifferentNames(true);
|
|
||||||
}
|
|
||||||
if (mapParams.containsKey("TargetsWithEqualToughness")) {
|
if (mapParams.containsKey("TargetsWithEqualToughness")) {
|
||||||
abTgt.setEqualToughness(true);
|
abTgt.setEqualToughness(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import java.util.Map.Entry;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
public class AbilityUtils {
|
public class AbilityUtils {
|
||||||
private final static ImmutableList<String> cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
|
private final static ImmutableList<String> cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
|
||||||
|
|
||||||
@@ -522,8 +523,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
} else if (calcX[0].equals("OriginalHost")) {
|
} else if (calcX[0].equals("OriginalHost")) {
|
||||||
val = xCount(ability.getOriginalHost(), calcX[1], ability);
|
val = xCount(ability.getOriginalHost(), calcX[1], ability);
|
||||||
} else if (calcX[0].equals("DungeonsCompleted")) {
|
|
||||||
val = handlePaid(player.getCompletedDungeons(), calcX[1], card, ability);
|
|
||||||
} else if (calcX[0].startsWith("ExiledWith")) {
|
} else if (calcX[0].startsWith("ExiledWith")) {
|
||||||
val = handlePaid(card.getExiledCards(), calcX[1], card, ability);
|
val = handlePaid(card.getExiledCards(), calcX[1], card, ability);
|
||||||
} else if (calcX[0].startsWith("Convoked")) {
|
} else if (calcX[0].startsWith("Convoked")) {
|
||||||
@@ -1329,6 +1328,12 @@ public class AbilityUtils {
|
|||||||
game.getTriggerHandler().resetActiveTriggers();
|
game.getTriggerHandler().resetActiveTriggers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("Precalc")) {
|
||||||
|
for (String s : sa.getParam("Precalc").split(",")) {
|
||||||
|
sa.setSVar(s, String.valueOf(calculateAmount(sa.getHostCard(), s, sa)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolvePreAbilities(sa, game);
|
resolvePreAbilities(sa, game);
|
||||||
|
|
||||||
// count times ability resolves this turn
|
// count times ability resolves this turn
|
||||||
@@ -1871,14 +1876,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
return doXMath(v, expr, c, ctb);
|
return doXMath(v, expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count$FromNamedAbility[abilityName].<True>.<False>
|
|
||||||
if (sq[0].startsWith("FromNamedAbility")) {
|
|
||||||
String abilityNamed = sq[0].substring(16);
|
|
||||||
SpellAbility trigSA = sa.getHostCard().getCastSA();
|
|
||||||
boolean fromNamedAbility = trigSA != null && trigSA.getName().equals(abilityNamed);
|
|
||||||
return doXMath(calculateAmount(c, sq[fromNamedAbility ? 1 : 2], ctb), expr, c, ctb);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// fallback if ctb isn't a spellability
|
// fallback if ctb isn't a spellability
|
||||||
if (sq[0].startsWith("LastStateBattlefield")) {
|
if (sq[0].startsWith("LastStateBattlefield")) {
|
||||||
@@ -2890,6 +2887,21 @@ public class AbilityUtils {
|
|||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sq[0].startsWith("DifferentCardNames_")) {
|
||||||
|
final List<String> crdname = Lists.newArrayList();
|
||||||
|
final String restriction = l[0].substring(19);
|
||||||
|
CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb);
|
||||||
|
// TODO rewrite with sharesName to respect Spy Kit
|
||||||
|
for (final Card card : list) {
|
||||||
|
String name = card.getName();
|
||||||
|
// CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common
|
||||||
|
if (!crdname.contains(name) && !name.isEmpty()) {
|
||||||
|
crdname.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(crdname.size(), expr, c, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("MostProminentCreatureType")) {
|
if (sq[0].startsWith("MostProminentCreatureType")) {
|
||||||
String restriction = l[0].split(" ")[1];
|
String restriction = l[0].split(" ")[1];
|
||||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||||
@@ -2904,6 +2916,13 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO move below to handlePaid
|
// TODO move below to handlePaid
|
||||||
|
if (sq[0].startsWith("SumPower")) {
|
||||||
|
final String[] restrictions = l[0].split("_");
|
||||||
|
int sumPower = game.getCardsIn(ZoneType.Battlefield).stream()
|
||||||
|
.filter(CardPredicates.restriction(restrictions[1], player, c, ctb))
|
||||||
|
.mapToInt(Card::getNetPower).sum();
|
||||||
|
return doXMath(sumPower, expr, c, ctb);
|
||||||
|
}
|
||||||
if (sq[0].startsWith("DifferentPower_")) {
|
if (sq[0].startsWith("DifferentPower_")) {
|
||||||
final String restriction = l[0].substring(15);
|
final String restriction = l[0].substring(15);
|
||||||
final int uniquePowers = (int) game.getCardsIn(ZoneType.Battlefield).stream()
|
final int uniquePowers = (int) game.getCardsIn(ZoneType.Battlefield).stream()
|
||||||
@@ -3423,7 +3442,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) {
|
public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) {
|
||||||
|
|
||||||
final String[] l = s.split("/");
|
final String[] l = s.split("/");
|
||||||
final String m = CardFactoryUtil.extractOperators(s);
|
final String m = CardFactoryUtil.extractOperators(s);
|
||||||
|
|
||||||
@@ -3610,10 +3628,46 @@ public class AbilityUtils {
|
|||||||
return doXMath(player.hasBeenDealtCombatDamageSinceLastTurn() ? 1 : 0, m, source, ctb);
|
return doXMath(player.hasBeenDealtCombatDamageSinceLastTurn() ? 1 : 0, m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.equals("DungeonsCompleted")) {
|
||||||
|
return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.equals("RingTemptedYou")) {
|
if (value.equals("RingTemptedYou")) {
|
||||||
return doXMath(player.getNumRingTemptedYou(), m, source, ctb);
|
return doXMath(player.getNumRingTemptedYou(), m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.startsWith("DungeonCompletedNamed")) {
|
||||||
|
String [] full = value.split("_");
|
||||||
|
String name = full[1];
|
||||||
|
int completed = 0;
|
||||||
|
List<Card> dungeons = player.getCompletedDungeons();
|
||||||
|
for (Card c : dungeons) {
|
||||||
|
if (c.getName().equals(name)) {
|
||||||
|
++completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(completed, m, source, ctb);
|
||||||
|
}
|
||||||
|
if (value.equals("DifferentlyNamedDungeonsCompleted")) {
|
||||||
|
int amount = 0;
|
||||||
|
List<Card> dungeons = player.getCompletedDungeons();
|
||||||
|
for (int i = 0; i < dungeons.size(); ++i) {
|
||||||
|
Card d1 = dungeons.get(i);
|
||||||
|
boolean hasSameName = false;
|
||||||
|
for (int j = i - 1; j >= 0; --j) {
|
||||||
|
Card d2 = dungeons.get(j);
|
||||||
|
if (d1.getName().equals(d2.getName())) {
|
||||||
|
hasSameName = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasSameName) {
|
||||||
|
++amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(amount, m, source, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.equals("AttractionsVisitedThisTurn")) {
|
if (value.equals("AttractionsVisitedThisTurn")) {
|
||||||
return doXMath(player.getAttractionsVisitedThisTurn(), m, source, ctb);
|
return doXMath(player.getAttractionsVisitedThisTurn(), m, source, ctb);
|
||||||
}
|
}
|
||||||
@@ -3692,6 +3746,10 @@ public class AbilityUtils {
|
|||||||
return CardLists.getTotalPower(paidList, ctb);
|
return CardLists.getTotalPower(paidList, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.startsWith("SumToughness")) {
|
||||||
|
return Aggregates.sum(paidList, Card::getNetToughness);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.startsWith("GreatestCMC")) {
|
if (string.startsWith("GreatestCMC")) {
|
||||||
return Aggregates.max(paidList, Card::getCMC);
|
return Aggregates.max(paidList, Card::getCMC);
|
||||||
}
|
}
|
||||||
@@ -3700,10 +3758,6 @@ public class AbilityUtils {
|
|||||||
return CardUtil.getColorsFromCards(paidList).countColors();
|
return CardUtil.getColorsFromCards(paidList).countColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.startsWith("DifferentCardNames")) {
|
|
||||||
return doXMath(CardLists.getDifferentNamesCount(paidList), CardFactoryUtil.extractOperators(string), source, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.equals("DifferentColorPair")) {
|
if (string.equals("DifferentColorPair")) {
|
||||||
final Set<ColorSet> diffPair = new HashSet<>();
|
final Set<ColorSet> diffPair = new HashSet<>();
|
||||||
for (final Card card : paidList) {
|
for (final Card card : paidList) {
|
||||||
|
|||||||
@@ -1069,7 +1069,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
// if ability was granted use that source so they can be kept apart later
|
// if ability was granted use that source so they can be kept apart later
|
||||||
if (cause.isCopiedTrait()) {
|
if (cause.isCopiedTrait()) {
|
||||||
exilingSource = cause.getOriginalHost();
|
exilingSource = cause.getOriginalHost();
|
||||||
} else if (!cause.isSpell() && cause.getKeyword() != null && cause.getKeyword().getStatic() != null) {
|
} else if (cause.getKeyword() != null && cause.getKeyword().getStatic() != null) {
|
||||||
exilingSource = cause.getKeyword().getStatic().getOriginalHost();
|
exilingSource = cause.getKeyword().getStatic().getOriginalHost();
|
||||||
}
|
}
|
||||||
movedCard.setExiledWith(exilingSource);
|
movedCard.setExiledWith(exilingSource);
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
final StringBuilder sb = new StringBuilder("Airbend ");
|
final StringBuilder sb = new StringBuilder("Airbend ");
|
||||||
|
|
||||||
Iterable<Card> tgts;
|
Iterable<Card> tgts;
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
tgts = getCardsfromTargets(sa);
|
tgts = getCardsfromTargets(sa);
|
||||||
} else { // otherwise add self to list and go from there
|
} else { // otherwise add self to list and go from there
|
||||||
tgts = sa.knownDetermineDefined(sa.getParam("Defined"));
|
tgts = sa.knownDetermineDefined(sa.getParam("Defined"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append(sa.getParamOrDefault("DefinedDesc", Lang.joinHomogenous(tgts)));
|
sb.append(sa.getParamOrDefault("DefinedDesc", Lang.joinHomogenous(tgts)));
|
||||||
sb.append(".");
|
sb.append(".");
|
||||||
if (Iterables.size(tgts) > 1) {
|
if (Iterables.size(tgts) > 1) {
|
||||||
@@ -39,6 +39,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Card hostCard = sa.getHostCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
@@ -46,7 +47,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
final Player pl = sa.getActivatingPlayer();
|
final Player pl = sa.getActivatingPlayer();
|
||||||
|
|
||||||
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
|
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
|
||||||
|
|
||||||
for (Card c : getTargetCards(sa)) {
|
for (Card c : getTargetCards(sa)) {
|
||||||
final Card gameCard = game.getCardState(c, null);
|
final Card gameCard = game.getCardState(c, null);
|
||||||
// gameCard is LKI in that case, the card is not in game anymore
|
// gameCard is LKI in that case, the card is not in game anymore
|
||||||
@@ -55,7 +56,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
if (gameCard == null || !c.equalsWithGameTimestamp(gameCard) || gameCard.isPhasedOut()) {
|
if (gameCard == null || !c.equalsWithGameTimestamp(gameCard) || gameCard.isPhasedOut()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gameCard.canExiledBy(sa, true)) {
|
if (!gameCard.canExiledBy(sa, true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
triggerList.triggerChangesZoneAll(game, sa);
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
handleExiledWith(triggerList.allCards(), sa);
|
handleExiledWith(triggerList.allCards(), sa);
|
||||||
|
|
||||||
pl.triggerElementalBend(TriggerType.Airbend);
|
pl.triggerElementalBend(TriggerType.Airbend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ public class AlterAttributeEffect extends SpellAbilityEffect {
|
|||||||
boolean altered = false;
|
boolean altered = false;
|
||||||
|
|
||||||
switch (attr.trim()) {
|
switch (attr.trim()) {
|
||||||
case "Harnessed":
|
|
||||||
altered = gameCard.setHarnessed(activate);
|
|
||||||
break;
|
|
||||||
case "Plotted":
|
case "Plotted":
|
||||||
altered = gameCard.setPlotted(activate);
|
altered = gameCard.setPlotted(activate);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardZoneTable;
|
import forge.game.card.CardZoneTable;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.card.token.TokenInfo;
|
import forge.game.card.token.TokenInfo;
|
||||||
import forge.game.event.GameEventCombatChanged;
|
import forge.game.event.GameEventCombatChanged;
|
||||||
import forge.game.event.GameEventTokenCreated;
|
import forge.game.event.GameEventTokenCreated;
|
||||||
@@ -85,7 +86,7 @@ public class AmassEffect extends TokenEffectBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> params = Maps.newHashMap();
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
params.put("CounterType", CounterEnumType.P1P1);
|
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
|
||||||
params.put("Amount", amount);
|
params.put("Amount", amount);
|
||||||
Card tgt = activator.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), false, params);
|
Card tgt = activator.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), false, params);
|
||||||
|
|
||||||
|
|||||||
@@ -90,16 +90,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
c.addPerpetual(p);
|
c.addPerpetual(p);
|
||||||
p.applyEffect(c);
|
p.applyEffect(c);
|
||||||
}
|
}
|
||||||
if (sa.hasParam("ManaCost")) {
|
|
||||||
final ManaCost manaCost = new ManaCost(new ManaCostParser(sa.getParam("ManaCost")));
|
|
||||||
if (perpetual) {
|
|
||||||
PerpetualManaCost p = new PerpetualManaCost(timestamp, manaCost);
|
|
||||||
c.addPerpetual(p);
|
|
||||||
p.applyEffect(c);
|
|
||||||
} else {
|
|
||||||
c.addChangedManaCost(manaCost, timestamp, (long) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!addType.isEmpty() || !removeType.isEmpty() || addAllCreatureTypes || !remove.isEmpty()) {
|
if (!addType.isEmpty() || !removeType.isEmpty() || addAllCreatureTypes || !remove.isEmpty()) {
|
||||||
if (perpetual) {
|
if (perpetual) {
|
||||||
@@ -138,7 +128,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
if (perpetual) {
|
if (perpetual) {
|
||||||
c.addPerpetual(new PerpetualColors(timestamp, colors, overwrite));
|
c.addPerpetual(new PerpetualColors(timestamp, colors, overwrite));
|
||||||
}
|
}
|
||||||
c.addColor(colors, !overwrite, timestamp, null);
|
c.addColor(colors, !overwrite, timestamp, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("LeaveBattlefield")) {
|
if (sa.hasParam("LeaveBattlefield")) {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class ChangeZoneEffect extends SpellAbilityEffect {
|
public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
@@ -104,7 +103,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
final String destination = sa.getParam("Destination");
|
final String destination = sa.getParam("Destination");
|
||||||
|
|
||||||
final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa) : 1;
|
|
||||||
String type = "card";
|
String type = "card";
|
||||||
boolean defined = false;
|
boolean defined = false;
|
||||||
if (sa.hasParam("ChangeTypeDesc")) {
|
if (sa.hasParam("ChangeTypeDesc")) {
|
||||||
@@ -119,11 +117,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
type = Lang.joinHomogenous(tgts);
|
type = Lang.joinHomogenous(tgts);
|
||||||
defined = true;
|
defined = true;
|
||||||
} else if (sa.hasParam("ChangeType") && !sa.getParam("ChangeType").equals("Card")) {
|
} else if (sa.hasParam("ChangeType") && !sa.getParam("ChangeType").equals("Card")) {
|
||||||
List<String> typeList = Arrays.stream(sa.getParam("ChangeType").split(",")).map(ct -> CardType.isACardType(ct) ? ct.toLowerCase() : ct).collect(Collectors.toList());
|
final String ct = sa.getParam("ChangeType");
|
||||||
type = Lang.joinHomogenous(typeList, null, num == 1 ? "or" : "and/or");
|
type = CardType.CoreType.isValidEnum(ct) ? ct.toLowerCase() : ct;
|
||||||
}
|
}
|
||||||
final String cardTag = type.contains("card") ? "" : " card";
|
final String cardTag = type.contains("card") ? "" : " card";
|
||||||
|
|
||||||
|
final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa) : 1;
|
||||||
boolean tapped = sa.hasParam("Tapped");
|
boolean tapped = sa.hasParam("Tapped");
|
||||||
boolean attacking = sa.hasParam("Attacking");
|
boolean attacking = sa.hasParam("Attacking");
|
||||||
if (sa.isNinjutsu()) {
|
if (sa.isNinjutsu()) {
|
||||||
@@ -153,9 +152,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
} else {
|
} else {
|
||||||
sb.append(" for ");
|
sb.append(" for ");
|
||||||
}
|
}
|
||||||
if (num != 1) {
|
|
||||||
sb.append(" up to ");
|
|
||||||
}
|
|
||||||
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
|
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
|
||||||
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination) != null && ZoneType.smartValueOf(destination).isHidden()) {
|
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination) != null && ZoneType.smartValueOf(destination).isHidden()) {
|
||||||
if (choosers.size() == 1) {
|
if (choosers.size() == 1) {
|
||||||
@@ -932,7 +928,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
List<ZoneType> origin = Lists.newArrayList();
|
List<ZoneType> origin = Lists.newArrayList();
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
|
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||||
}
|
}
|
||||||
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
|
|
||||||
@@ -973,10 +969,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
String prompt;
|
String prompt;
|
||||||
if (sa.hasParam("OptionalPrompt")) {
|
if (sa.hasParam("OptionalPrompt")) {
|
||||||
prompt = sa.getParam("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 {
|
} else {
|
||||||
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
|
if (defined) {
|
||||||
|
prompt = Localizer.getInstance().getMessage("lblPutThatCardFromPlayerOriginToDestination", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase(), destination.getTranslatedName().toLowerCase());
|
||||||
|
} else {
|
||||||
|
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
String message = MessageUtil.formatMessage(prompt , decider, player);
|
String message = MessageUtil.formatMessage(prompt , decider, player);
|
||||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
|
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
|
||||||
@@ -1478,7 +1476,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
if (ZoneType.Exile.equals(destination) && sa.hasParam("WithCountersType")) {
|
if (ZoneType.Exile.equals(destination) && sa.hasParam("WithCountersType")) {
|
||||||
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
||||||
int cAmount = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
int cAmount = AbilityUtils.calculateAmount(sa.getOriginalHost(), sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
movedCard.addCounter(cType, cAmount, player, table);
|
movedCard.addCounter(cType, cAmount, player, table);
|
||||||
table.replaceCounterEffect(game, sa, true);
|
table.replaceCounterEffect(game, sa, true);
|
||||||
|
|||||||
@@ -287,17 +287,22 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
|||||||
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
||||||
// need to create a physical card first, i need the original card faces
|
// need to create a physical card first, i need the original card faces
|
||||||
copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame());
|
copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame());
|
||||||
|
|
||||||
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
|
|
||||||
// force update the now set State
|
|
||||||
if (original.isTransformable()) {
|
if (original.isTransformable()) {
|
||||||
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
|
||||||
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
||||||
// the resulting token is a transforming token that has both a front face and a back face.
|
// the resulting token is a transforming token that has both a front face and a back face.
|
||||||
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
||||||
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
||||||
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
||||||
copy.setBackSide(original.isBackSide());
|
copy.setBackSide(original.isBackSide());
|
||||||
|
if (original.isTransformed()) {
|
||||||
|
copy.incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
|
||||||
|
// force update the now set State
|
||||||
|
if (original.isTransformable()) {
|
||||||
|
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
||||||
} else {
|
} else {
|
||||||
copy.setState(copy.getCurrentStateName(), true, true);
|
copy.setState(copy.getCurrentStateName(), true, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,29 +102,24 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
int totalRemoved = 0;
|
int totalRemoved = 0;
|
||||||
CardCollectionView srcCards;
|
CardCollectionView srcCards;
|
||||||
|
|
||||||
if (sa.hasParam("Choices")) {
|
if (sa.hasParam("Choices")) {
|
||||||
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
||||||
: ZoneType.Battlefield;
|
: ZoneType.Battlefield;
|
||||||
srcCards = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
|
||||||
activator, source, sa);
|
|
||||||
} else {
|
|
||||||
srcCards = getTargetCards(sa);
|
|
||||||
}
|
|
||||||
if (sa.isReplacementAbility()) {
|
|
||||||
srcCards = new CardCollection(srcCards).filter(c -> !c.isInPlay() || sa.getLastStateBattlefield().contains(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.hasParam("Choices")) {
|
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
||||||
|
activator, source, sa);
|
||||||
|
|
||||||
int min = 1;
|
int min = 1;
|
||||||
int max = 1;
|
int max = 1;
|
||||||
if (sa.hasParam("ChoiceOptional")) {
|
if (sa.hasParam("ChoiceOptional")) {
|
||||||
min = 0;
|
min = 0;
|
||||||
max = srcCards.size();
|
max = choices.size();
|
||||||
}
|
}
|
||||||
if (sa.hasParam("ChoiceNum")) {
|
if (sa.hasParam("ChoiceNum")) {
|
||||||
min = max = AbilityUtils.calculateAmount(source, sa.getParam("ChoiceNum"), sa);
|
min = max = AbilityUtils.calculateAmount(source, sa.getParam("ChoiceNum"), sa);
|
||||||
}
|
}
|
||||||
if (srcCards.size() < min) {
|
if (choices.size() < min) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +128,13 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
title = title.replace(" ", " ");
|
title = title.replace(" ", " ");
|
||||||
Map<String, Object> params = Maps.newHashMap();
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
params.put("CounterType", counterType);
|
params.put("CounterType", counterType);
|
||||||
srcCards = pc.chooseCardsForEffect(srcCards, sa, title, min, max, min == 0, params);
|
srcCards = pc.chooseCardsForEffect(choices, sa, title, min, max, min == 0, params);
|
||||||
} else {
|
} else {
|
||||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||||
if (!tgtPlayer.isInGame()) {
|
if (!tgtPlayer.isInGame()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Removing energy
|
||||||
if (type.equals("All")) {
|
if (type.equals("All")) {
|
||||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
||||||
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||||
@@ -154,6 +150,8 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srcCards = getTargetCards(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Card tgtCard : srcCards) {
|
for (final Card tgtCard : srcCards) {
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ public class DamageResolveEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
CardDamageMap damageMap = sa.getDamageMap();
|
CardDamageMap damageMap = sa.getDamageMap();
|
||||||
if (damageMap == null) {
|
|
||||||
// this can happen if damagesource was missing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CardDamageMap preventMap = sa.getPreventMap();
|
CardDamageMap preventMap = sa.getPreventMap();
|
||||||
GameEntityCounterTable counterTable = sa.getCounterTable();
|
GameEntityCounterTable counterTable = sa.getCounterTable();
|
||||||
|
|
||||||
|
|||||||
@@ -73,11 +73,6 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||||
if (zone.equals(ZoneType.Battlefield)) {
|
|
||||||
if (sa.hasParam("Tapped")) {
|
|
||||||
made.setTapped(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (zone.equals(ZoneType.Exile)) {
|
if (zone.equals(ZoneType.Exile)) {
|
||||||
handleExiledWith(made, sa);
|
handleExiledWith(made, sa);
|
||||||
if (sa.hasParam("ExileFaceDown")) {
|
if (sa.hasParam("ExileFaceDown")) {
|
||||||
|
|||||||
@@ -40,14 +40,13 @@ public class EarthbendEffect extends SpellAbilityEffect {
|
|||||||
TargetRestrictions abTgt = new TargetRestrictions("Select target land you control", "Land.YouCtrl".split(","), "1", "1");
|
TargetRestrictions abTgt = new TargetRestrictions("Select target land you control", "Land.YouCtrl".split(","), "1", "1");
|
||||||
sa.setTargetRestrictions(abTgt);
|
sa.setTargetRestrictions(abTgt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
final Player pl = sa.getActivatingPlayer();
|
final Player pl = sa.getActivatingPlayer();
|
||||||
int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("Num", "1"), sa);
|
int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("Num", "1"), sa);
|
||||||
|
|
||||||
long ts = game.getNextTimestamp();
|
long ts = game.getNextTimestamp();
|
||||||
|
|
||||||
String desc = "When it dies or is exiled, return it to the battlefield tapped.";
|
String desc = "When it dies or is exiled, return it to the battlefield tapped.";
|
||||||
@@ -59,17 +58,17 @@ public class EarthbendEffect extends SpellAbilityEffect {
|
|||||||
c.addNewPT(0, 0, ts, 0);
|
c.addNewPT(0, 0, ts, 0);
|
||||||
c.addChangedCardTypes(Arrays.asList("Creature"), null, false, EnumSet.noneOf(RemoveType.class), ts, 0, true, false);
|
c.addChangedCardTypes(Arrays.asList("Creature"), null, false, EnumSet.noneOf(RemoveType.class), ts, 0, true, false);
|
||||||
c.addChangedCardKeywords(Arrays.asList("Haste"), null, false, ts, null);
|
c.addChangedCardKeywords(Arrays.asList("Haste"), null, false, ts, null);
|
||||||
|
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
c.addCounter(CounterEnumType.P1P1, num, pl, table);
|
c.addCounter(CounterEnumType.P1P1, num, pl, table);
|
||||||
table.replaceCounterEffect(game, sa, true);
|
table.replaceCounterEffect(game, sa, true);
|
||||||
|
|
||||||
buildTrigger(sa, c, sbTrigA, "Graveyard");
|
buildTrigger(sa, c, sbTrigA, "Graveyard");
|
||||||
buildTrigger(sa, c, sbTrigB, "Exile");
|
buildTrigger(sa, c, sbTrigB, "Exile");
|
||||||
}
|
}
|
||||||
pl.triggerElementalBend(TriggerType.Earthbend);
|
pl.triggerElementalBend(TriggerType.Earthbend);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void buildTrigger(SpellAbility sa, Card c, String sbTrig, String zone) {
|
protected void buildTrigger(SpellAbility sa, Card c, String sbTrig, String zone) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
|
|||||||
@@ -269,22 +269,22 @@ public class EffectEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Color(s)
|
||||||
if (hostCard.hasChosenColor()) {
|
if (hostCard.hasChosenColor()) {
|
||||||
eff.setChosenColors(Lists.newArrayList(hostCard.getChosenColors()));
|
eff.setChosenColors(Lists.newArrayList(hostCard.getChosenColors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Cards
|
||||||
if (hostCard.hasChosenCard()) {
|
if (hostCard.hasChosenCard()) {
|
||||||
eff.setChosenCards(hostCard.getChosenCards());
|
eff.setChosenCards(hostCard.getChosenCards());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Player
|
||||||
if (hostCard.hasChosenPlayer()) {
|
if (hostCard.hasChosenPlayer()) {
|
||||||
eff.setChosenPlayer(hostCard.getChosenPlayer());
|
eff.setChosenPlayer(hostCard.getChosenPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hostCard.getChosenDirection() != null) {
|
// Set Chosen Type
|
||||||
eff.setChosenDirection(hostCard.getChosenDirection());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hostCard.hasChosenType()) {
|
if (hostCard.hasChosenType()) {
|
||||||
eff.setChosenType(hostCard.getChosenType());
|
eff.setChosenType(hostCard.getChosenType());
|
||||||
}
|
}
|
||||||
@@ -292,10 +292,12 @@ public class EffectEffect extends SpellAbilityEffect {
|
|||||||
eff.setChosenType2(hostCard.getChosenType2());
|
eff.setChosenType2(hostCard.getChosenType2());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen name
|
||||||
if (hostCard.hasNamedCard()) {
|
if (hostCard.hasNamedCard()) {
|
||||||
eff.setNamedCards(Lists.newArrayList(hostCard.getNamedCards()));
|
eff.setNamedCards(Lists.newArrayList(hostCard.getNamedCards()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chosen number
|
||||||
if (sa.hasParam("SetChosenNumber")) {
|
if (sa.hasParam("SetChosenNumber")) {
|
||||||
eff.setChosenNumber(AbilityUtils.calculateAmount(hostCard, sa.getParam("SetChosenNumber"), sa));
|
eff.setChosenNumber(AbilityUtils.calculateAmount(hostCard, sa.getParam("SetChosenNumber"), sa));
|
||||||
} else if (hostCard.hasChosenNumber()) {
|
} else if (hostCard.hasChosenNumber()) {
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
|
|||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Game game = host.getGame();
|
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);
|
int amt = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TriggerAmount", "1"), sa);
|
||||||
if (amt <= 0) {
|
if (amt <= 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -54,29 +54,33 @@ public class LifeExchangeEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
final int life1 = p1.getLife();
|
final int life1 = p1.getLife();
|
||||||
final int life2 = p2.getLife();
|
final int life2 = p2.getLife();
|
||||||
final int diff = Math.abs(life1 - life2);
|
|
||||||
|
|
||||||
if (life2 > life1) {
|
if (sa.hasParam("RememberDifference")) {
|
||||||
// swap players
|
final int diff = life1 - life2;
|
||||||
Player tmp = p2;
|
source.addRemembered(diff);
|
||||||
p2 = p1;
|
|
||||||
p1 = tmp;
|
|
||||||
}
|
}
|
||||||
if (diff > 0 && p1.canLoseLife() && p2.canGainLife()) {
|
|
||||||
|
final Map<Player, Integer> lossMap = Maps.newHashMap();
|
||||||
|
if ((life1 > life2) && p1.canLoseLife() && p2.canGainLife()) {
|
||||||
|
final int diff = life1 - life2;
|
||||||
final int lost = p1.loseLife(diff, false, false);
|
final int lost = p1.loseLife(diff, false, false);
|
||||||
p2.gainLife(diff, source, sa);
|
p2.gainLife(diff, source, sa);
|
||||||
if (lost > 0) {
|
if (lost > 0) {
|
||||||
final Map<Player, Integer> lossMap = Maps.newHashMap();
|
|
||||||
lossMap.put(p1, lost);
|
lossMap.put(p1, lost);
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
|
|
||||||
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
|
|
||||||
if (sa.hasParam("RememberOwnLoss") && p1.equals(sa.getActivatingPlayer())) {
|
|
||||||
source.addRemembered(lost);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if ((life2 > life1) && p2.canLoseLife() && p1.canGainLife()) {
|
||||||
|
final int diff = life2 - life1;
|
||||||
|
final int lost = p2.loseLife(diff, false, false);
|
||||||
|
p1.gainLife(diff, source, sa);
|
||||||
|
if (lost > 0) {
|
||||||
|
lossMap.put(p2, lost);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// they are equal or can't be exchanged, so nothing to do
|
||||||
}
|
}
|
||||||
if (sa.hasParam("RememberDifference")) {
|
if (!lossMap.isEmpty()) { // Run triggers if any player actually lost life
|
||||||
source.addRemembered(p1.getLife() - p2.getLife());
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
|
||||||
|
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,11 +271,10 @@ public class ManaEffect extends SpellAbilityEffect {
|
|||||||
producedMana.append(abMana.produceMana(mana, p, sa));
|
producedMana.append(abMana.produceMana(mana, p, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only clear express choice after mana has been produced
|
|
||||||
abMana.clearExpressChoice();
|
|
||||||
|
|
||||||
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
|
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
|
||||||
|
|
||||||
|
// Only clear express choice after mana has been produced
|
||||||
|
abMana.clearExpressChoice();
|
||||||
if (sa.isKeyword(Keyword.FIREBENDING)) {
|
if (sa.isKeyword(Keyword.FIREBENDING)) {
|
||||||
activator.triggerElementalBend(TriggerType.Firebend);
|
activator.triggerElementalBend(TriggerType.Firebend);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ public class PermanentCreatureEffect extends PermanentEffect {
|
|||||||
public String getStackDescription(final SpellAbility sa) {
|
public String getStackDescription(final SpellAbility sa) {
|
||||||
final CardState source = sa.getCardState();
|
final CardState source = sa.getCardState();
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ");
|
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ").append(source.getBasePowerString());
|
||||||
sb.append(sa.getParamOrDefault("SetPower", source.getBasePowerString()));
|
sb.append(" / ").append(source.getBaseToughnessString());
|
||||||
sb.append(" / ").append(sa.getParamOrDefault("SetToughness", source.getBaseToughnessString()));
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,10 +428,6 @@ public class PlayEffect extends SpellAbilityEffect {
|
|||||||
tgtSA.getTargetRestrictions().setMandatory(true);
|
tgtSA.getTargetRestrictions().setMandatory(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Named")) {
|
|
||||||
tgtSA.setName(sa.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// can't be done later
|
// can't be done later
|
||||||
if (sa.hasParam("ReplaceGraveyard")) {
|
if (sa.hasParam("ReplaceGraveyard")) {
|
||||||
if (!sa.hasParam("ReplaceGraveyardValid")
|
if (!sa.hasParam("ReplaceGraveyardValid")
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import forge.game.GameEntity;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardFactoryUtil;
|
import forge.game.card.CardFactoryUtil;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.card.perpetual.PerpetualKeywords;
|
import forge.game.card.perpetual.PerpetualKeywords;
|
||||||
@@ -281,17 +282,6 @@ public class PumpEffect extends SpellAbilityEffect {
|
|||||||
List<Card> tgtCards = getCardsfromTargets(sa);
|
List<Card> tgtCards = getCardsfromTargets(sa);
|
||||||
List<Player> tgtPlayers = getTargetPlayers(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();
|
List<String> keywords = Lists.newArrayList();
|
||||||
if (sa.hasParam("KW")) {
|
if (sa.hasParam("KW")) {
|
||||||
keywords.addAll(Arrays.asList(sa.getParam("KW").split(" & ")));
|
keywords.addAll(Arrays.asList(sa.getParam("KW").split(" & ")));
|
||||||
@@ -317,6 +307,8 @@ public class PumpEffect extends SpellAbilityEffect {
|
|||||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, host, sa);
|
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, host, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final CardCollection untargetedCards = CardUtil.getRadiance(sa);
|
||||||
|
|
||||||
if (sa.hasParam("DefinedKW")) {
|
if (sa.hasParam("DefinedKW")) {
|
||||||
String defined = sa.getParam("DefinedKW");
|
String defined = sa.getParam("DefinedKW");
|
||||||
if (defined.equals("ChosenType")) {
|
if (defined.equals("ChosenType")) {
|
||||||
@@ -402,6 +394,17 @@ public class PumpEffect extends SpellAbilityEffect {
|
|||||||
keywords = choice;
|
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")) {
|
if (sa.hasParam("RememberObjects")) {
|
||||||
host.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("RememberObjects"), sa));
|
host.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("RememberObjects"), sa));
|
||||||
}
|
}
|
||||||
@@ -491,7 +494,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
|||||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), tgtCards);
|
registerDelayedTrigger(sa, sa.getParam("AtEOT"), tgtCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Card tgtC : CardUtil.getRadiance(sa)) {
|
for (final Card tgtC : untargetedCards) {
|
||||||
// only pump things in PumpZone
|
// only pump things in PumpZone
|
||||||
if (!tgtC.isInZones(pumpZones)) {
|
if (!tgtC.isInZones(pumpZones)) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -75,8 +75,9 @@ public class RestartGameEffect extends SpellAbilityEffect {
|
|||||||
p.clearController();
|
p.clearController();
|
||||||
|
|
||||||
CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false));
|
CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false));
|
||||||
|
List<Card> filteredCards = null;
|
||||||
if (leaveZone != null) {
|
if (leaveZone != null) {
|
||||||
List<Card> filteredCards = CardLists.getValidCards(p.getCardsIn(leaveZone), leaveRestriction, p, sa.getHostCard(), sa);
|
filteredCards = CardLists.getValidCards(p.getCardsIn(leaveZone), leaveRestriction, p, sa.getHostCard(), sa);
|
||||||
newLibrary.addAll(filteredCards);
|
newLibrary.addAll(filteredCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -369,8 +369,8 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
|||||||
List<Card> canIncrementDice = new ArrayList<>();
|
List<Card> canIncrementDice = new ArrayList<>();
|
||||||
for (Card c : xenosquirrels) {
|
for (Card c : xenosquirrels) {
|
||||||
// Xenosquirrels must have a P1P1 counter on it to remove in order to modify
|
// Xenosquirrels must have a P1P1 counter on it to remove in order to modify
|
||||||
Integer P1P1Counters = c.getCounters().get(CounterEnumType.P1P1);
|
Integer P1P1Counters = c.getCounters().get(CounterType.get(CounterEnumType.P1P1));
|
||||||
if (P1P1Counters != null && P1P1Counters > 0 && c.canRemoveCounters(CounterEnumType.P1P1)) {
|
if (P1P1Counters != null && P1P1Counters > 0 && c.canRemoveCounters(CounterType.get(CounterEnumType.P1P1))) {
|
||||||
canIncrementDice.add(c);
|
canIncrementDice.add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,7 +399,6 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
|||||||
* @param repParams replacement effect parameters
|
* @param repParams replacement effect parameters
|
||||||
* @return list of final roll results after applying ignores and replacements, sorted in ascending order
|
* @return list of final roll results after applying ignores and replacements, sorted in ascending order
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static List<Integer> rollAction(int amount, int sides, int ignore, List<Integer> rollsResult, List<Integer> ignored, Map<Player, Integer> ignoreChosenMap, Set<Card> dicePTExchanges, Player player, Map<AbilityKey, Object> repParams) {
|
private static List<Integer> rollAction(int amount, int sides, int ignore, List<Integer> rollsResult, List<Integer> ignored, Map<Player, Integer> ignoreChosenMap, Set<Card> dicePTExchanges, Player player, Map<AbilityKey, Object> repParams) {
|
||||||
|
|
||||||
repParams.put(AbilityKey.Sides, sides);
|
repParams.put(AbilityKey.Sides, sides);
|
||||||
@@ -417,8 +416,6 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
|||||||
ignoreChosenMap = (Map<Player, Integer>) repParams.get(AbilityKey.IgnoreChosen);
|
ignoreChosenMap = (Map<Player, Integer>) repParams.get(AbilityKey.IgnoreChosen);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Integer> naturalRolls = (rollsResult == null ? new ArrayList<>() : rollsResult);
|
List<Integer> naturalRolls = (rollsResult == null ? new ArrayList<>() : rollsResult);
|
||||||
|
|||||||
@@ -92,11 +92,12 @@ public class SacrificeEffect extends SpellAbilityEffect {
|
|||||||
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
|
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
|
||||||
|
|
||||||
if (valid.equals("Self") && game.getZoneOf(host) != null) {
|
if (valid.equals("Self") && game.getZoneOf(host) != null) {
|
||||||
if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield) &&
|
if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield)) {
|
||||||
(!optional || activator.getController().confirmAction(sa, null,
|
if (!optional || activator.getController().confirmAction(sa, null,
|
||||||
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null))) {
|
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null)) {
|
||||||
if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) {
|
if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) {
|
||||||
host.addRemembered(host);
|
host.addRemembered(host);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ import forge.game.GameEntityCounterTable;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterType;
|
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerController;
|
import forge.game.player.PlayerController;
|
||||||
@@ -21,6 +19,7 @@ import forge.game.spellability.SpellAbility;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.CardTranslation;
|
import forge.util.CardTranslation;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
|
import forge.util.collect.FCollection;
|
||||||
|
|
||||||
public class TimeTravelEffect extends SpellAbilityEffect {
|
public class TimeTravelEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
@@ -38,11 +37,13 @@ public class TimeTravelEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
PlayerController pc = activator.getController();
|
PlayerController pc = activator.getController();
|
||||||
|
|
||||||
final CounterType counterType = CounterEnumType.TIME;
|
final CounterEnumType counterType = CounterEnumType.TIME;
|
||||||
|
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
|
FCollection<Card> list = new FCollection<>();
|
||||||
|
|
||||||
// card you own that is suspended
|
// card you own that is suspended
|
||||||
CardCollection list = CardLists.filter(activator.getCardsIn(ZoneType.Exile), CardPredicates.hasSuspend());
|
list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Exile), CardPredicates.hasSuspend()));
|
||||||
// permanent you control with time counter
|
// permanent you control with time counter
|
||||||
list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.hasCounter(counterType)));
|
list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.hasCounter(counterType)));
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
private boolean unearthed;
|
private boolean unearthed;
|
||||||
private boolean ringbearer;
|
private boolean ringbearer;
|
||||||
private boolean monstrous;
|
private boolean monstrous;
|
||||||
private boolean harnessed;
|
|
||||||
private boolean renowned;
|
private boolean renowned;
|
||||||
private boolean solved;
|
private boolean solved;
|
||||||
private boolean tributed;
|
private boolean tributed;
|
||||||
@@ -257,7 +256,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|
|
||||||
private long worldTimestamp = -1;
|
private long worldTimestamp = -1;
|
||||||
private long bestowTimestamp = -1;
|
private long bestowTimestamp = -1;
|
||||||
private long transformedTimestamp = -1;
|
private long transformedTimestamp = 0;
|
||||||
private long prototypeTimestamp = -1;
|
private long prototypeTimestamp = -1;
|
||||||
private long mutatedTimestamp = -1;
|
private long mutatedTimestamp = -1;
|
||||||
private int timesMutated = 0;
|
private int timesMutated = 0;
|
||||||
@@ -425,7 +424,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
public long getPrototypeTimestamp() { return prototypeTimestamp; }
|
public long getPrototypeTimestamp() { return prototypeTimestamp; }
|
||||||
|
|
||||||
public long getTransformedTimestamp() { return transformedTimestamp; }
|
public long getTransformedTimestamp() { return transformedTimestamp; }
|
||||||
public void setTransformedTimestamp(long ts) { this.transformedTimestamp = ts; }
|
public void incrementTransformedTimestamp() { this.transformedTimestamp++; }
|
||||||
|
public void undoIncrementTransformedTimestamp() { this.transformedTimestamp--; }
|
||||||
|
|
||||||
// The following methods are used to selectively update certain view components (text,
|
// The following methods are used to selectively update certain view components (text,
|
||||||
// P/T, card types) in order to avoid card flickering due to aggressive full update
|
// P/T, card types) in order to avoid card flickering due to aggressive full update
|
||||||
@@ -449,7 +449,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|
|
||||||
public final void updateColorForView() {
|
public final void updateColorForView() {
|
||||||
currentState.getView().updateColors(this);
|
currentState.getView().updateColors(this);
|
||||||
currentState.getView().updateHasChangeColors(hasChangedCardColors());
|
currentState.getView().updateHasChangeColors(!Iterables.isEmpty(getChangedCardColors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAttackingForView() {
|
public void updateAttackingForView() {
|
||||||
@@ -695,7 +695,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
||||||
getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false);
|
getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false);
|
||||||
}
|
}
|
||||||
setTransformedTimestamp(ts);
|
incrementTransformedTimestamp();
|
||||||
|
|
||||||
return retResult;
|
return retResult;
|
||||||
} else if (mode.equals("Flip")) {
|
} else if (mode.equals("Flip")) {
|
||||||
@@ -965,7 +965,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
String name = state.getName();
|
String name = state.getName();
|
||||||
for (CardChangedName change : this.changedCardNames.values()) {
|
for (CardChangedName change : this.changedCardNames.values()) {
|
||||||
if (change.isOverwrite()) {
|
if (change.isOverwrite()) {
|
||||||
name = change.newName();
|
name = change.getNewName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return alt ? StaticData.instance().getCommonCards().getName(name, true) : name;
|
return alt ? StaticData.instance().getCommonCards().getName(name, true) : name;
|
||||||
@@ -980,7 +980,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
for (CardChangedName change : this.changedCardNames.values()) {
|
for (CardChangedName change : this.changedCardNames.values()) {
|
||||||
if (change.isOverwrite()) {
|
if (change.isOverwrite()) {
|
||||||
result = false;
|
result = false;
|
||||||
} else if (change.addNonLegendaryCreatureNames()) {
|
} else if (change.isAddNonLegendaryCreatureNames()) {
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1013,12 +1013,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
currentState.getView().updateName(currentState);
|
currentState.getView().updateName(currentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CardChangedName(String newName, boolean addNonLegendaryCreatureNames) {
|
|
||||||
public boolean isOverwrite() {
|
|
||||||
return newName != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGamePieceType(GamePieceType gamePieceType) {
|
public void setGamePieceType(GamePieceType gamePieceType) {
|
||||||
this.gamePieceType = gamePieceType;
|
this.gamePieceType = gamePieceType;
|
||||||
this.view.updateGamePieceType(this);
|
this.view.updateGamePieceType(this);
|
||||||
@@ -1075,7 +1069,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isDoubleFaced() {
|
public final boolean isDoubleFaced() {
|
||||||
return isTransformable() || isMeldable();
|
return isTransformable() || isMeldable() || isModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isFlipCard() {
|
public final boolean isFlipCard() {
|
||||||
@@ -1137,10 +1131,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isTransformed() {
|
public final boolean isTransformed() {
|
||||||
if (isMeldable() || hasMergedCard()) {
|
return getTransformedTimestamp() != 0;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.isTransformable() && isBackSide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isFlipped() {
|
public final boolean isFlipped() {
|
||||||
@@ -2271,7 +2262,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|
|
||||||
public final ColorSet getMarkedColors() {
|
public final ColorSet getMarkedColors() {
|
||||||
if (markedColor == null) {
|
if (markedColor == null) {
|
||||||
return ColorSet.NO_COLORS;
|
return ColorSet.getNullColor();
|
||||||
}
|
}
|
||||||
return markedColor;
|
return markedColor;
|
||||||
}
|
}
|
||||||
@@ -2459,11 +2450,22 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
} else if (keyword.startsWith("DeckLimit")) {
|
} else if (keyword.startsWith("DeckLimit")) {
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
sbLong.append(k[2]).append("\r\n");
|
sbLong.append(k[2]).append("\r\n");
|
||||||
} else if (keyword.startsWith("Enchant") && inst instanceof KeywordWithType kwt) {
|
} else if (keyword.startsWith("Enchant")) {
|
||||||
String desc = kwt.getTypeDescription();
|
String m[] = keyword.split(":");
|
||||||
|
String desc;
|
||||||
|
if (m.length > 2) {
|
||||||
|
desc = m[2];
|
||||||
|
} else {
|
||||||
|
desc = m[1];
|
||||||
|
if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) {
|
||||||
|
desc = desc.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
sbLong.append("Enchant ").append(desc).append("\r\n");
|
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")
|
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|
||||||
|| keyword.startsWith("Disguise") || keyword.startsWith("Reflect")
|
|| keyword.startsWith("Disguise")
|
||||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||||
|| keyword.startsWith("Madness:")|| keyword.startsWith("Recover")
|
|| keyword.startsWith("Madness:")|| keyword.startsWith("Recover")
|
||||||
|| keyword.startsWith("Reconfigure") || keyword.startsWith("Squad")
|
|| keyword.startsWith("Reconfigure") || keyword.startsWith("Squad")
|
||||||
@@ -2502,15 +2504,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
sbLong.append(".");
|
sbLong.append(".");
|
||||||
}
|
}
|
||||||
if (k.length > 3) {
|
if (k.length > 3) {
|
||||||
sbLong.append(". ").append(k[3]);
|
sbLong.append(". " + k[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sbLong.append(" (").append(inst.getReminderText()).append(")");
|
sbLong.append(" (").append(inst.getReminderText()).append(")");
|
||||||
sbLong.append("\r\n");
|
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")) {
|
} else if (keyword.startsWith("Echo")) {
|
||||||
sbLong.append("Echo ");
|
sbLong.append("Echo ");
|
||||||
final String[] upkeepCostParams = keyword.split(":");
|
final String[] upkeepCostParams = keyword.split(":");
|
||||||
@@ -2604,7 +2608,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|
||||||
|| keyword.equals("Daybound") || keyword.equals("Nightbound")
|
|| keyword.equals("Daybound") || keyword.equals("Nightbound")
|
||||||
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")
|
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")
|
||||||
|| keyword.equals("Partner - Father & Son") || keyword.equals("Partner - Survivors")
|
|
||||||
|| keyword.equals("Space sculptor") || keyword.equals("Doctor's companion")
|
|| keyword.equals("Space sculptor") || keyword.equals("Doctor's companion")
|
||||||
|| keyword.equals("Start your engines")) {
|
|| keyword.equals("Start your engines")) {
|
||||||
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
||||||
@@ -2650,7 +2653,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing:")
|
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing:")
|
||||||
|| keyword.startsWith("Afterlife") || keyword.startsWith("Hideaway") || keyword.startsWith("Toxic")
|
|| keyword.startsWith("Afterlife") || keyword.startsWith("Hideaway") || keyword.startsWith("Toxic")
|
||||||
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|
||||||
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Ripple")) {
|
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator")) {
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
sbLong.append(k[0]).append(" ").append(k[1]).append(" (").append(inst.getReminderText()).append(")");
|
sbLong.append(k[0]).append(" ").append(k[1]).append(" (").append(inst.getReminderText()).append(")");
|
||||||
} else if (keyword.startsWith("Crew")) {
|
} else if (keyword.startsWith("Crew")) {
|
||||||
@@ -2966,9 +2969,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
if (monstrous) {
|
if (monstrous) {
|
||||||
sb.append("Monstrous\r\n");
|
sb.append("Monstrous\r\n");
|
||||||
}
|
}
|
||||||
if (harnessed) {
|
|
||||||
sb.append("Harnessed\r\n");
|
|
||||||
}
|
|
||||||
if (renowned) {
|
if (renowned) {
|
||||||
sb.append("Renowned\r\n");
|
sb.append("Renowned\r\n");
|
||||||
}
|
}
|
||||||
@@ -3561,8 +3561,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
if (!getStaticAbilities().isEmpty()) {
|
if (!getStaticAbilities().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!getReplacementEffects().isEmpty()
|
if (!getReplacementEffects().isEmpty()) {
|
||||||
&& (getReplacementEffects().size() > 1 || !isSaga() || hasKeyword(Keyword.READ_AHEAD))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!getTriggers().isEmpty()) {
|
if (!getTriggers().isEmpty()) {
|
||||||
@@ -3795,7 +3794,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
public final void addLeavesPlayCommand(final GameCommand c) {
|
public final void addLeavesPlayCommand(final GameCommand c) {
|
||||||
leavePlayCommandList.add(c);
|
leavePlayCommandList.add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addStaticCommandList(Object[] objects) {
|
public void addStaticCommandList(Object[] objects) {
|
||||||
staticCommandList.add(objects);
|
staticCommandList.add(objects);
|
||||||
}
|
}
|
||||||
@@ -4297,10 +4296,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean clearChangedCardColors() {
|
public boolean clearChangedCardColors() {
|
||||||
boolean changed = hasChangedCardColors();
|
boolean changed = false;
|
||||||
|
|
||||||
|
if (!changedCardColorsByText.isEmpty())
|
||||||
|
changed = true;
|
||||||
changedCardColorsByText.clear();
|
changedCardColorsByText.clear();
|
||||||
|
|
||||||
|
if (!changedCardTypesCharacterDefining.isEmpty())
|
||||||
|
changed = true;
|
||||||
changedCardTypesCharacterDefining.clear();
|
changedCardTypesCharacterDefining.clear();
|
||||||
|
|
||||||
|
if (!changedCardColors.isEmpty())
|
||||||
|
changed = true;
|
||||||
changedCardColors.clear();
|
changedCardColors.clear();
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
@@ -4386,19 +4393,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasChangedCardColors() {
|
public Iterable<CardColor> getChangedCardColors() {
|
||||||
return !changedCardColorsByText.isEmpty() || !changedCardColorsCharacterDefining.isEmpty() || !changedCardColors.isEmpty();
|
return Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addColorByText(final ColorSet color, final long timestamp, final StaticAbility stAb) {
|
public void addColorByText(final ColorSet color, final long timestamp, final long staticId) {
|
||||||
changedCardColorsByText.put(timestamp, (long)stAb.getId(), new CardColor(color, false));
|
changedCardColorsByText.put(timestamp, staticId, new CardColor(color, false));
|
||||||
updateColorForView();
|
updateColorForView();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void addColor(final ColorSet color, final boolean addToColors, final long timestamp, final StaticAbility stAb) {
|
public final void addColor(final ColorSet color, final boolean addToColors, final long timestamp, final long staticId, final boolean cda) {
|
||||||
(stAb != null && stAb.isCharacteristicDefining() ? changedCardColorsCharacterDefining : changedCardColors).put(
|
(cda ? changedCardColorsCharacterDefining : changedCardColors).put(timestamp, staticId, new CardColor(color, addToColors));
|
||||||
timestamp, stAb != null ? stAb.getId() : (long)0, new CardColor(color, addToColors)
|
|
||||||
);
|
|
||||||
updateColorForView();
|
updateColorForView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4425,20 +4430,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
public final ColorSet getColor(CardState state) {
|
public final ColorSet getColor(CardState state) {
|
||||||
byte colors = state.getColor();
|
byte colors = state.getColor();
|
||||||
for (final CardColor cc : Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values())) {
|
for (final CardColor cc : getChangedCardColors()) {
|
||||||
if (cc.additional()) {
|
if (cc.isAdditional()) {
|
||||||
colors |= cc.color().getColor();
|
colors |= cc.getColorMask();
|
||||||
} else {
|
} else {
|
||||||
colors = cc.color().getColor();
|
colors = cc.getColorMask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ColorSet.fromMask(colors);
|
return ColorSet.fromMask(colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CardColor(ColorSet color, boolean additional) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public final int getCurrentLoyalty() {
|
public final int getCurrentLoyalty() {
|
||||||
return getCounters(CounterEnumType.LOYALTY);
|
return getCounters(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
@@ -4808,7 +4809,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
public void addDraftAction(String s) {
|
public void addDraftAction(String s) {
|
||||||
draftActions.add(s);
|
draftActions.add(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int intensity = 0;
|
private int intensity = 0;
|
||||||
public final void addIntensity(final int n) {
|
public final void addIntensity(final int n) {
|
||||||
intensity += n;
|
intensity += n;
|
||||||
@@ -6440,10 +6441,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|
|
||||||
DamageType damageType = DamageType.Normal;
|
DamageType damageType = DamageType.Normal;
|
||||||
if (isPlaneswalker()) { // 120.3c
|
if (isPlaneswalker()) { // 120.3c
|
||||||
subtractCounter(CounterEnumType.LOYALTY, damageIn, null, true);
|
subtractCounter(CounterType.get(CounterEnumType.LOYALTY), damageIn, null, true);
|
||||||
}
|
}
|
||||||
if (isBattle()) {
|
if (isBattle()) {
|
||||||
subtractCounter(CounterEnumType.DEFENSE, damageIn, null, true);
|
subtractCounter(CounterType.get(CounterEnumType.DEFENSE), damageIn, null, true);
|
||||||
}
|
}
|
||||||
if (isCreature()) {
|
if (isCreature()) {
|
||||||
if (source.isWitherDamage()) { // 120.3d
|
if (source.isWitherDamage()) { // 120.3d
|
||||||
@@ -6691,14 +6692,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
setRingBearer(false);
|
setRingBearer(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isHarnessed() {
|
|
||||||
return harnessed;
|
|
||||||
}
|
|
||||||
public final boolean setHarnessed(final boolean harnessed0) {
|
|
||||||
harnessed = harnessed0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean isMonstrous() {
|
public final boolean isMonstrous() {
|
||||||
return monstrous;
|
return monstrous;
|
||||||
}
|
}
|
||||||
@@ -6856,10 +6849,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
return exiledSA.isKeyword(Keyword.WARP);
|
return exiledSA.isKeyword(Keyword.WARP);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWebSlinged() {
|
|
||||||
return getCastSA() != null && getCastSA().isAlternativeCost(AlternativeCost.WebSlinging);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSpecialized() {
|
public boolean isSpecialized() {
|
||||||
return specialized;
|
return specialized;
|
||||||
}
|
}
|
||||||
@@ -7155,7 +7144,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StaticAbilityCantTarget.cantTarget(this, sa) != null) {
|
if (StaticAbilityCantTarget.cantTarget(this, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7167,62 +7156,51 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final String cantBeEnchantedByMsg(final Card aura) {
|
protected final boolean canBeEnchantedBy(final Card aura) {
|
||||||
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
||||||
return "No Enchant Keyword";
|
return false;
|
||||||
}
|
}
|
||||||
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||||
if (ki instanceof KeywordWithType kwt) {
|
String k = ki.getOriginal();
|
||||||
String v = kwt.getValidType();
|
String m[] = k.split(":");
|
||||||
String desc = kwt.getTypeDescription();
|
String v = m[1];
|
||||||
if (!isValid(v.split(","), aura.getController(), aura, null) || (!v.contains("inZone") && !isInPlay())) {
|
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||||
return getName() + " is not " + Lang.nounWithAmount(1, desc);
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!v.contains("inZone") && !isInPlay()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String cantBeEquippedByMsg(final Card equip, SpellAbility sa) {
|
protected final boolean canBeEquippedBy(final Card equip, SpellAbility sa) {
|
||||||
if (!isInPlay()) {
|
if (!isInPlay()) {
|
||||||
return getName() + " is not in play";
|
return false;
|
||||||
}
|
}
|
||||||
if (sa != null && sa.isEquip()) {
|
if (sa != null && sa.isEquip()) {
|
||||||
if (!isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa)) {
|
return isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa);
|
||||||
Equip eq = (Equip) sa.getKeyword();
|
|
||||||
return getName() + " is not " + Lang.nounWithAmount(1, eq.getValidDescription());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
if (!isCreature()) {
|
return isCreature();
|
||||||
return getName() + " is not a creature";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String cantBeFortifiedByMsg(final Card fort) {
|
protected boolean canBeFortifiedBy(final Card fort) {
|
||||||
if (!isLand()) {
|
return isLand() && isInPlay() && !fort.isLand();
|
||||||
return getName() + " is not a Land";
|
|
||||||
}
|
|
||||||
if (!isInPlay()) {
|
|
||||||
return getName() + " is not in play";
|
|
||||||
}
|
|
||||||
if (fort.isLand()) {
|
|
||||||
return fort.getName() + " is a Land";
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.GameEntity#canBeAttached(forge.game.card.Card, boolean)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) {
|
public boolean canBeAttached(Card attach, SpellAbility sa, boolean checkSBA) {
|
||||||
|
// phase check there
|
||||||
if (isPhasedOut() && !attach.isPhasedOut()) {
|
if (isPhasedOut() && !attach.isPhasedOut()) {
|
||||||
return getName() + " is phased out";
|
return false;
|
||||||
}
|
}
|
||||||
return super.cantBeAttachedMsg(attach, sa, checkSBA);
|
|
||||||
|
return super.canBeAttached(attach, sa, checkSBA);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean canBeSacrificedBy(final SpellAbility source, final boolean effect) {
|
public final boolean canBeSacrificedBy(final SpellAbility source, final boolean effect) {
|
||||||
@@ -7647,6 +7625,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
if (sa.isBestow()) {
|
if (sa.isBestow()) {
|
||||||
animateBestow();
|
animateBestow();
|
||||||
}
|
}
|
||||||
|
if (sa.isDisturb() || sa.hasParam("CastTransformed")) {
|
||||||
|
incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
if (sa.hasParam("Prototype") && prototypeTimestamp == -1) {
|
if (sa.hasParam("Prototype") && prototypeTimestamp == -1) {
|
||||||
long next = game.getNextTimestamp();
|
long next = game.getNextTimestamp();
|
||||||
addCloneState(CardFactory.getCloneStates(this, this, sa), next);
|
addCloneState(CardFactory.getCloneStates(this, this, sa), next);
|
||||||
@@ -7856,8 +7837,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
return currentState.getUntranslatedType();
|
return currentState.getUntranslatedType();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String getTranslatedName() {
|
public String getUntranslatedOracle() {
|
||||||
return CardTranslation.getTranslatedName(this);
|
return currentState.getUntranslatedOracle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package forge.game.card;
|
||||||
|
|
||||||
|
public class CardChangedName {
|
||||||
|
|
||||||
|
protected String newName;
|
||||||
|
protected boolean addNonLegendaryCreatureNames = false;
|
||||||
|
|
||||||
|
public CardChangedName(String newName, boolean addNonLegendaryCreatureNames) {
|
||||||
|
this.newName = newName;
|
||||||
|
this.addNonLegendaryCreatureNames = addNonLegendaryCreatureNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewName() {
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOverwrite() {
|
||||||
|
return newName != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAddNonLegendaryCreatureNames() {
|
||||||
|
return addNonLegendaryCreatureNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
forge-game/src/main/java/forge/game/card/CardColor.java
Normal file
45
forge-game/src/main/java/forge/game/card/CardColor.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Forge: Play Magic: the Gathering.
|
||||||
|
* Copyright (C) 2011 Forge Team
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package forge.game.card;
|
||||||
|
|
||||||
|
import forge.card.ColorSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Card_Color class.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Forge
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class CardColor {
|
||||||
|
private final byte colorMask;
|
||||||
|
public final byte getColorMask() {
|
||||||
|
return colorMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean additional;
|
||||||
|
public final boolean isAdditional() {
|
||||||
|
return this.additional;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardColor(final ColorSet colors, final boolean addToColors) {
|
||||||
|
this.colorMask = colors.getColor();
|
||||||
|
this.additional = addToColors;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -131,7 +131,9 @@ public class CardCopyService {
|
|||||||
|
|
||||||
c.setState(in.getCurrentStateName(), false);
|
c.setState(in.getCurrentStateName(), false);
|
||||||
c.setRules(in.getRules());
|
c.setRules(in.getRules());
|
||||||
c.setBackSide(in.isBackSide());
|
if (in.isTransformed()) {
|
||||||
|
c.incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -166,6 +168,9 @@ public class CardCopyService {
|
|||||||
// The characteristics of its front and back face are determined by the copiable values of the same face of the spell it is a copy of, as modified by any other copy effects.
|
// The characteristics of its front and back face are determined by the copiable values of the same face of the spell it is a copy of, as modified by any other copy effects.
|
||||||
// If the spell it is a copy of has its back face up, the copy is created with its back face up. The token that’s put onto the battlefield as that spell resolves is a transforming token.
|
// If the spell it is a copy of has its back face up, the copy is created with its back face up. The token that’s put onto the battlefield as that spell resolves is a transforming token.
|
||||||
to.setBackSide(copyFrom.isBackSide());
|
to.setBackSide(copyFrom.isBackSide());
|
||||||
|
if (copyFrom.isTransformed()) {
|
||||||
|
to.incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
} else if (fromIsTransformedCard) {
|
} else if (fromIsTransformedCard) {
|
||||||
copyState(copyFrom, copyFrom.getCurrentStateName(), to, CardStateName.Original);
|
copyState(copyFrom, copyFrom.getCurrentStateName(), to, CardStateName.Original);
|
||||||
} else {
|
} else {
|
||||||
@@ -269,6 +274,9 @@ public class CardCopyService {
|
|||||||
}
|
}
|
||||||
newCopy.setFlipped(copyFrom.isFlipped());
|
newCopy.setFlipped(copyFrom.isFlipped());
|
||||||
newCopy.setBackSide(copyFrom.isBackSide());
|
newCopy.setBackSide(copyFrom.isBackSide());
|
||||||
|
if (copyFrom.isTransformed()) {
|
||||||
|
newCopy.incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
if (newCopy.hasAlternateState()) {
|
if (newCopy.hasAlternateState()) {
|
||||||
newCopy.setState(copyFrom.getCurrentStateName(), false, true);
|
newCopy.setState(copyFrom.getCurrentStateName(), false, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,16 +87,22 @@ public class CardFactory {
|
|||||||
// need to create a physical card first, i need the original card faces
|
// need to create a physical card first, i need the original card faces
|
||||||
final Card copy = getCard(original.getPaperCard(), controller, id, game);
|
final Card copy = getCard(original.getPaperCard(), controller, id, game);
|
||||||
|
|
||||||
copy.setStates(getCloneStates(original, copy, sourceSA));
|
|
||||||
// force update the now set State
|
|
||||||
if (original.isTransformable()) {
|
if (original.isTransformable()) {
|
||||||
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
|
||||||
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
||||||
// the resulting token is a transforming token that has both a front face and a back face.
|
// the resulting token is a transforming token that has both a front face and a back face.
|
||||||
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
||||||
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
||||||
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
||||||
copy.setBackSide(original.isBackSide());
|
copy.setBackSide(original.isBackSide());
|
||||||
|
if (original.isTransformed()) {
|
||||||
|
copy.incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy.setStates(getCloneStates(original, copy, sourceSA));
|
||||||
|
// force update the now set State
|
||||||
|
if (original.isTransformable()) {
|
||||||
|
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
||||||
} else {
|
} else {
|
||||||
copy.setState(copy.getCurrentStateName(), true, true);
|
copy.setState(copy.getCurrentStateName(), true, true);
|
||||||
}
|
}
|
||||||
@@ -370,28 +376,22 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Negative card Id's are for view purposes only
|
// Build English oracle and translated oracle mapping
|
||||||
if (c.getId() >= 0) {
|
if (c.getId() >= 0) {
|
||||||
// Build English oracle and translated oracle mapping
|
|
||||||
CardTranslation.buildOracleMapping(face.getName(), face.getOracleText(), variantName);
|
CardTranslation.buildOracleMapping(face.getName(), face.getOracleText(), variantName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set name for Sentry reports to be identifiable
|
// Name first so Senty has the Card name
|
||||||
c.setName(face.getName());
|
c.setName(face.getName());
|
||||||
|
|
||||||
if (c.getId() >= 0) { // Set Triggers & Abilities if not for view
|
for (Entry<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
|
||||||
for (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()));
|
|
||||||
|
|
||||||
// keywords not before variables
|
for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true, c.getCurrentState()));
|
||||||
c.addIntrinsicKeywords(face.getKeywords(), false);
|
for (String s : face.getStaticAbilities()) c.addStaticAbility(s);
|
||||||
}
|
for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true, c.getCurrentState()));
|
||||||
|
|
||||||
|
// keywords not before variables
|
||||||
|
c.addIntrinsicKeywords(face.getKeywords(), false);
|
||||||
if (face.getDraftActions() != null) {
|
if (face.getDraftActions() != null) {
|
||||||
face.getDraftActions().forEach(c::addDraftAction);
|
face.getDraftActions().forEach(c::addDraftAction);
|
||||||
}
|
}
|
||||||
@@ -420,8 +420,7 @@ public class CardFactory {
|
|||||||
|
|
||||||
c.setAttractionLights(face.getAttractionLights());
|
c.setAttractionLights(face.getAttractionLights());
|
||||||
|
|
||||||
if (c.getId() > 0) // Set FactoryAbilities if not for view
|
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
|
||||||
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void copySpellAbility(SpellAbility from, SpellAbility to, final Card host, final Player p, final boolean lki, final boolean keepTextChanges) {
|
public static void copySpellAbility(SpellAbility from, SpellAbility to, final Card host, final Player p, final boolean lki, final boolean keepTextChanges) {
|
||||||
@@ -466,29 +465,29 @@ public class CardFactory {
|
|||||||
return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, controller, false), sa.getDecider());
|
return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, controller, false), sa.getDecider());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase cause) {
|
public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) {
|
||||||
final Card host = cause.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Map<String,String> origSVars = host.getSVars();
|
final Map<String,String> origSVars = host.getSVars();
|
||||||
final List<String> types = Lists.newArrayList();
|
final List<String> types = Lists.newArrayList();
|
||||||
final List<String> keywords = Lists.newArrayList();
|
final List<String> keywords = Lists.newArrayList();
|
||||||
boolean KWifNew = false;
|
boolean KWifNew = false;
|
||||||
final List<String> removeKeywords = Lists.newArrayList();
|
final List<String> removeKeywords = Lists.newArrayList();
|
||||||
List<String> creatureTypes = null;
|
List<String> creatureTypes = null;
|
||||||
final CardCloneStates result = new CardCloneStates(in, cause);
|
final CardCloneStates result = new CardCloneStates(in, sa);
|
||||||
|
|
||||||
final String newName = cause.getParam("NewName");
|
final String newName = sa.getParam("NewName");
|
||||||
ColorSet colors = null;
|
ColorSet colors = null;
|
||||||
|
|
||||||
if (cause.hasParam("AddTypes")) {
|
if (sa.hasParam("AddTypes")) {
|
||||||
types.addAll(Arrays.asList(cause.getParam("AddTypes").split(" & ")));
|
types.addAll(Arrays.asList(sa.getParam("AddTypes").split(" & ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetCreatureTypes")) {
|
if (sa.hasParam("SetCreatureTypes")) {
|
||||||
creatureTypes = ImmutableList.copyOf(cause.getParam("SetCreatureTypes").split(" "));
|
creatureTypes = ImmutableList.copyOf(sa.getParam("SetCreatureTypes").split(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("AddKeywords")) {
|
if (sa.hasParam("AddKeywords")) {
|
||||||
String kwString = cause.getParam("AddKeywords");
|
String kwString = sa.getParam("AddKeywords");
|
||||||
if (kwString.startsWith("IfNew ")) {
|
if (kwString.startsWith("IfNew ")) {
|
||||||
KWifNew = true;
|
KWifNew = true;
|
||||||
kwString = kwString.substring(6);
|
kwString = kwString.substring(6);
|
||||||
@@ -496,21 +495,21 @@ public class CardFactory {
|
|||||||
keywords.addAll(Arrays.asList(kwString.split(" & ")));
|
keywords.addAll(Arrays.asList(kwString.split(" & ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("RemoveKeywords")) {
|
if (sa.hasParam("RemoveKeywords")) {
|
||||||
removeKeywords.addAll(Arrays.asList(cause.getParam("RemoveKeywords").split(" & ")));
|
removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("AddColors")) {
|
if (sa.hasParam("AddColors")) {
|
||||||
colors = ColorSet.fromNames(cause.getParam("AddColors").split(","));
|
colors = ColorSet.fromNames(sa.getParam("AddColors").split(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetColor")) {
|
if (sa.hasParam("SetColor")) {
|
||||||
colors = ColorSet.fromNames(cause.getParam("SetColor").split(","));
|
colors = ColorSet.fromNames(sa.getParam("SetColor").split(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetColorByManaCost")) {
|
if (sa.hasParam("SetColorByManaCost")) {
|
||||||
if (cause.hasParam("SetManaCost")) {
|
if (sa.hasParam("SetManaCost")) {
|
||||||
colors = ColorSet.fromManaCost(new ManaCost(new ManaCostParser(cause.getParam("SetManaCost"))));
|
colors = ColorSet.fromManaCost(new ManaCost(new ManaCostParser(sa.getParam("SetManaCost"))));
|
||||||
} else {
|
} else {
|
||||||
colors = ColorSet.fromManaCost(host.getManaCost());
|
colors = ColorSet.fromManaCost(host.getManaCost());
|
||||||
}
|
}
|
||||||
@@ -522,55 +521,56 @@ public class CardFactory {
|
|||||||
// if something is cloning a facedown card, it only clones the
|
// if something is cloning a facedown card, it only clones the
|
||||||
// facedown state into original
|
// facedown state into original
|
||||||
final CardState ret = new CardState(out, CardStateName.Original);
|
final CardState ret = new CardState(out, CardStateName.Original);
|
||||||
ret.copyFrom(in.getFaceDownState(), false, cause);
|
ret.copyFrom(in.getFaceDownState(), false, sa);
|
||||||
result.put(CardStateName.Original, ret);
|
result.put(CardStateName.Original, ret);
|
||||||
} else if (in.isFlipCard()) {
|
} else if (in.isFlipCard()) {
|
||||||
// if something is cloning a flip card, copy both original and
|
// if something is cloning a flip card, copy both original and
|
||||||
// flipped state
|
// flipped state
|
||||||
final CardState ret1 = new CardState(out, CardStateName.Original);
|
final CardState ret1 = new CardState(out, CardStateName.Original);
|
||||||
ret1.copyFrom(in.getState(CardStateName.Original), false, cause);
|
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
|
||||||
result.put(CardStateName.Original, ret1);
|
result.put(CardStateName.Original, ret1);
|
||||||
|
|
||||||
final CardState ret2 = new CardState(out, CardStateName.Flipped);
|
final CardState ret2 = new CardState(out, CardStateName.Flipped);
|
||||||
ret2.copyFrom(in.getState(CardStateName.Flipped), false, cause);
|
ret2.copyFrom(in.getState(CardStateName.Flipped), false, sa);
|
||||||
result.put(CardStateName.Flipped, ret2);
|
result.put(CardStateName.Flipped, ret2);
|
||||||
} else if (in.hasState(CardStateName.Secondary)) {
|
} else if (in.hasState(CardStateName.Secondary)) {
|
||||||
final CardState ret1 = new CardState(out, CardStateName.Original);
|
final CardState ret1 = new CardState(out, CardStateName.Original);
|
||||||
ret1.copyFrom(in.getState(CardStateName.Original), false, cause);
|
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
|
||||||
result.put(CardStateName.Original, ret1);
|
result.put(CardStateName.Original, ret1);
|
||||||
|
|
||||||
final CardState ret2 = new CardState(out, CardStateName.Secondary);
|
final CardState ret2 = new CardState(out, CardStateName.Secondary);
|
||||||
ret2.copyFrom(in.getState(CardStateName.Secondary), false, cause);
|
ret2.copyFrom(in.getState(CardStateName.Secondary), false, sa);
|
||||||
result.put(CardStateName.Secondary, ret2);
|
result.put(CardStateName.Secondary, ret2);
|
||||||
} else if (in.isTransformable() && cause instanceof SpellAbility sa && (
|
} else if (in.isTransformable() && sa instanceof SpellAbility && (
|
||||||
ApiType.CopyPermanent.equals(sa.getApi()) ||
|
ApiType.CopyPermanent.equals(((SpellAbility)sa).getApi()) ||
|
||||||
ApiType.CopySpellAbility.equals(sa.getApi()) ||
|
ApiType.CopySpellAbility.equals(((SpellAbility)sa).getApi()) ||
|
||||||
ApiType.ReplaceToken.equals(sa.getApi()))) {
|
ApiType.ReplaceToken.equals(((SpellAbility)sa).getApi())
|
||||||
|
)) {
|
||||||
// CopyPermanent can copy token
|
// CopyPermanent can copy token
|
||||||
final CardState ret1 = new CardState(out, CardStateName.Original);
|
final CardState ret1 = new CardState(out, CardStateName.Original);
|
||||||
ret1.copyFrom(in.getState(CardStateName.Original), false, cause);
|
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
|
||||||
result.put(CardStateName.Original, ret1);
|
result.put(CardStateName.Original, ret1);
|
||||||
|
|
||||||
final CardState ret2 = new CardState(out, CardStateName.Backside);
|
final CardState ret2 = new CardState(out, CardStateName.Backside);
|
||||||
ret2.copyFrom(in.getState(CardStateName.Backside), false, cause);
|
ret2.copyFrom(in.getState(CardStateName.Backside), false, sa);
|
||||||
result.put(CardStateName.Backside, ret2);
|
result.put(CardStateName.Backside, ret2);
|
||||||
} else if (in.isSplitCard()) {
|
} else if (in.isSplitCard()) {
|
||||||
// for split cards, copy all three states
|
// for split cards, copy all three states
|
||||||
final CardState ret1 = new CardState(out, CardStateName.Original);
|
final CardState ret1 = new CardState(out, CardStateName.Original);
|
||||||
ret1.copyFrom(in.getState(CardStateName.Original), false, cause);
|
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
|
||||||
result.put(CardStateName.Original, ret1);
|
result.put(CardStateName.Original, ret1);
|
||||||
|
|
||||||
final CardState ret2 = new CardState(out, CardStateName.LeftSplit);
|
final CardState ret2 = new CardState(out, CardStateName.LeftSplit);
|
||||||
ret2.copyFrom(in.getState(CardStateName.LeftSplit), false, cause);
|
ret2.copyFrom(in.getState(CardStateName.LeftSplit), false, sa);
|
||||||
result.put(CardStateName.LeftSplit, ret2);
|
result.put(CardStateName.LeftSplit, ret2);
|
||||||
|
|
||||||
final CardState ret3 = new CardState(out, CardStateName.RightSplit);
|
final CardState ret3 = new CardState(out, CardStateName.RightSplit);
|
||||||
ret3.copyFrom(in.getState(CardStateName.RightSplit), false, cause);
|
ret3.copyFrom(in.getState(CardStateName.RightSplit), false, sa);
|
||||||
result.put(CardStateName.RightSplit, ret3);
|
result.put(CardStateName.RightSplit, ret3);
|
||||||
} else {
|
} else {
|
||||||
// in all other cases just copy the current state to original
|
// in all other cases just copy the current state to original
|
||||||
final CardState ret = new CardState(out, CardStateName.Original);
|
final CardState ret = new CardState(out, CardStateName.Original);
|
||||||
ret.copyFrom(in.getState(in.getCurrentStateName()), false, cause);
|
ret.copyFrom(in.getState(in.getCurrentStateName()), false, sa);
|
||||||
result.put(CardStateName.Original, ret);
|
result.put(CardStateName.Original, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,32 +580,32 @@ public class CardFactory {
|
|||||||
final CardState state = e.getValue();
|
final CardState state = e.getValue();
|
||||||
|
|
||||||
// has Embalm Condition for extra changes of Vizier of Many Faces
|
// has Embalm Condition for extra changes of Vizier of Many Faces
|
||||||
if (cause.hasParam("Embalm") && !out.isEmbalmed()) {
|
if (sa.hasParam("Embalm") && !out.isEmbalmed()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the names for the states
|
// update the names for the states
|
||||||
if (cause.hasParam("KeepName")) {
|
if (sa.hasParam("KeepName")) {
|
||||||
state.setName(originalState.getName());
|
state.setName(originalState.getName());
|
||||||
} else if (newName != null) {
|
} else if (newName != null) {
|
||||||
// convert NICKNAME descriptions?
|
// convert NICKNAME descriptions?
|
||||||
state.setName(newName);
|
state.setName(newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("AddColors")) {
|
if (sa.hasParam("AddColors")) {
|
||||||
state.addColor(colors.getColor());
|
state.addColor(colors.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) {
|
if (sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) {
|
||||||
state.setColor(colors.getColor());
|
state.setColor(colors.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("NonLegendary")) {
|
if (sa.hasParam("NonLegendary")) {
|
||||||
state.removeType(CardType.Supertype.Legendary);
|
state.removeType(CardType.Supertype.Legendary);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("RemoveCardTypes")) {
|
if (sa.hasParam("RemoveCardTypes")) {
|
||||||
state.removeCardTypes(cause.hasParam("RemoveSubTypes"));
|
state.removeCardTypes(sa.hasParam("RemoveSubTypes"));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.addType(types);
|
state.addType(types);
|
||||||
@@ -637,31 +637,31 @@ public class CardFactory {
|
|||||||
|
|
||||||
// CR 208.3 A noncreature object not on the battlefield has power or toughness only if it has a power and toughness printed on it.
|
// CR 208.3 A noncreature object not on the battlefield has power or toughness only if it has a power and toughness printed on it.
|
||||||
// currently only LKI can be trusted?
|
// currently only LKI can be trusted?
|
||||||
if ((cause.hasParam("SetPower") || cause.hasParam("SetToughness")) &&
|
if ((sa.hasParam("SetPower") || sa.hasParam("SetToughness")) &&
|
||||||
(state.getType().isCreature() || (originalState != null && in.getOriginalState(originalState.getStateName()).getBasePowerString() != null))) {
|
(state.getType().isCreature() || (originalState != null && in.getOriginalState(originalState.getStateName()).getBasePowerString() != null))) {
|
||||||
if (cause.hasParam("SetPower")) {
|
if (sa.hasParam("SetPower")) {
|
||||||
state.setBasePower(AbilityUtils.calculateAmount(host, cause.getParam("SetPower"), cause));
|
state.setBasePower(AbilityUtils.calculateAmount(host, sa.getParam("SetPower"), sa));
|
||||||
}
|
}
|
||||||
if (cause.hasParam("SetToughness")) {
|
if (sa.hasParam("SetToughness")) {
|
||||||
state.setBaseToughness(AbilityUtils.calculateAmount(host, cause.getParam("SetToughness"), cause));
|
state.setBaseToughness(AbilityUtils.calculateAmount(host, sa.getParam("SetToughness"), sa));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.getType().isPlaneswalker() && cause.hasParam("SetLoyalty")) {
|
if (state.getType().isPlaneswalker() && sa.hasParam("SetLoyalty")) {
|
||||||
state.setBaseLoyalty(String.valueOf(AbilityUtils.calculateAmount(host, cause.getParam("SetLoyalty"), cause)));
|
state.setBaseLoyalty(String.valueOf(AbilityUtils.calculateAmount(host, sa.getParam("SetLoyalty"), sa)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("RemoveCost")) {
|
if (sa.hasParam("RemoveCost")) {
|
||||||
state.setManaCost(ManaCost.NO_COST);
|
state.setManaCost(ManaCost.NO_COST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetManaCost")) {
|
if (sa.hasParam("SetManaCost")) {
|
||||||
state.setManaCost(new ManaCost(new ManaCostParser(cause.getParam("SetManaCost"))));
|
state.setManaCost(new ManaCost(new ManaCostParser(sa.getParam("SetManaCost"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVars to add to clone
|
// SVars to add to clone
|
||||||
if (cause.hasParam("AddSVars") || cause.hasParam("GainTextSVars")) {
|
if (sa.hasParam("AddSVars") || sa.hasParam("GainTextSVars")) {
|
||||||
final String str = cause.getParamOrDefault("GainTextSVars", cause.getParam("AddSVars"));
|
final String str = sa.getParamOrDefault("GainTextSVars", sa.getParam("AddSVars"));
|
||||||
for (final String s : str.split(",")) {
|
for (final String s : str.split(",")) {
|
||||||
if (origSVars.containsKey(s)) {
|
if (origSVars.containsKey(s)) {
|
||||||
final String actualsVar = origSVars.get(s);
|
final String actualsVar = origSVars.get(s);
|
||||||
@@ -671,8 +671,8 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// triggers to add to clone
|
// triggers to add to clone
|
||||||
if (cause.hasParam("AddTriggers")) {
|
if (sa.hasParam("AddTriggers")) {
|
||||||
for (final String s : cause.getParam("AddTriggers").split(",")) {
|
for (final String s : sa.getParam("AddTriggers").split(",")) {
|
||||||
if (origSVars.containsKey(s)) {
|
if (origSVars.containsKey(s)) {
|
||||||
final String actualTrigger = origSVars.get(s);
|
final String actualTrigger = origSVars.get(s);
|
||||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true, state);
|
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true, state);
|
||||||
@@ -682,8 +682,8 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// abilities to add to clone
|
// abilities to add to clone
|
||||||
if (cause.hasParam("AddAbilities") || cause.hasParam("GainTextAbilities")) {
|
if (sa.hasParam("AddAbilities") || sa.hasParam("GainTextAbilities")) {
|
||||||
final String str = cause.getParamOrDefault("GainTextAbilities", cause.getParam("AddAbilities"));
|
final String str = sa.getParamOrDefault("GainTextAbilities", sa.getParam("AddAbilities"));
|
||||||
for (final String s : str.split(",")) {
|
for (final String s : str.split(",")) {
|
||||||
if (origSVars.containsKey(s)) {
|
if (origSVars.containsKey(s)) {
|
||||||
final String actualAbility = origSVars.get(s);
|
final String actualAbility = origSVars.get(s);
|
||||||
@@ -695,18 +695,18 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// static abilities to add to clone
|
// static abilities to add to clone
|
||||||
if (cause.hasParam("AddStaticAbilities")) {
|
if (sa.hasParam("AddStaticAbilities")) {
|
||||||
final String str = cause.getParam("AddStaticAbilities");
|
final String str = sa.getParam("AddStaticAbilities");
|
||||||
for (final String s : str.split(",")) {
|
for (final String s : str.split(",")) {
|
||||||
if (origSVars.containsKey(s)) {
|
if (origSVars.containsKey(s)) {
|
||||||
final String actualStatic = origSVars.get(s);
|
final String actualStatic = origSVars.get(s);
|
||||||
state.addStaticAbility(StaticAbility.create(actualStatic, out, cause.getCardState(), true));
|
state.addStaticAbility(StaticAbility.create(actualStatic, out, sa.getCardState(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("GainThisAbility") && cause instanceof SpellAbility sa) {
|
if (sa.hasParam("GainThisAbility") && sa instanceof SpellAbility) {
|
||||||
SpellAbility root = sa.getRootAbility();
|
SpellAbility root = ((SpellAbility) sa).getRootAbility();
|
||||||
|
|
||||||
// Aurora Shifter
|
// Aurora Shifter
|
||||||
if (root.isTrigger() && root.getTrigger().getSpawningAbility() != null) {
|
if (root.isTrigger() && root.getTrigger().getSpawningAbility() != null) {
|
||||||
@@ -723,35 +723,35 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special Rules for Embalm and Eternalize
|
// Special Rules for Embalm and Eternalize
|
||||||
if (cause.isEmbalm() && cause.isIntrinsic()) {
|
if (sa.isEmbalm() && sa.isIntrinsic()) {
|
||||||
String name = "embalm_" + TextUtil.fastReplace(
|
String name = "embalm_" + TextUtil.fastReplace(
|
||||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||||
" ", "_").toLowerCase();
|
" ", "_").toLowerCase();
|
||||||
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.isEternalize() && cause.isIntrinsic()) {
|
if (sa.isEternalize() && sa.isIntrinsic()) {
|
||||||
String name = "eternalize_" + TextUtil.fastReplace(
|
String name = "eternalize_" + TextUtil.fastReplace(
|
||||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||||
" ", "_").toLowerCase();
|
" ", "_").toLowerCase();
|
||||||
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.isKeyword(Keyword.OFFSPRING) && cause.isIntrinsic()) {
|
if (sa.isKeyword(Keyword.OFFSPRING) && sa.isIntrinsic()) {
|
||||||
String name = "offspring_" + TextUtil.fastReplace(
|
String name = "offspring_" + TextUtil.fastReplace(
|
||||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||||
" ", "_").toLowerCase();
|
" ", "_").toLowerCase();
|
||||||
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.isKeyword(Keyword.SQUAD) && cause.isIntrinsic()) {
|
if (sa.isKeyword(Keyword.SQUAD) && sa.isIntrinsic()) {
|
||||||
String name = "squad_" + TextUtil.fastReplace(
|
String name = "squad_" + TextUtil.fastReplace(
|
||||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||||
" ", "_").toLowerCase();
|
" ", "_").toLowerCase();
|
||||||
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("GainTextOf") && originalState != null) {
|
if (sa.hasParam("GainTextOf") && originalState != null) {
|
||||||
state.setSetCode(originalState.getSetCode());
|
state.setSetCode(originalState.getSetCode());
|
||||||
state.setRarity(originalState.getRarity());
|
state.setRarity(originalState.getRarity());
|
||||||
state.setImageKey(originalState.getImageKey());
|
state.setImageKey(originalState.getImageKey());
|
||||||
@@ -763,27 +763,27 @@ public class CardFactory {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetPower") && sta.hasParam("SetPower"))
|
if (sa.hasParam("SetPower") && sta.hasParam("SetPower"))
|
||||||
state.removeStaticAbility(sta);
|
state.removeStaticAbility(sta);
|
||||||
|
|
||||||
if (cause.hasParam("SetToughness") && sta.hasParam("SetToughness"))
|
if (sa.hasParam("SetToughness") && sta.hasParam("SetToughness"))
|
||||||
state.removeStaticAbility(sta);
|
state.removeStaticAbility(sta);
|
||||||
|
|
||||||
// currently only Changeling and similar should be affected by that
|
// currently only Changeling and similar should be affected by that
|
||||||
// other cards using AddType$ ChosenType should not
|
// other cards using AddType$ ChosenType should not
|
||||||
if (cause.hasParam("SetCreatureTypes") && sta.hasParam("AddAllCreatureTypes")) {
|
if (sa.hasParam("SetCreatureTypes") && sta.hasParam("AddAllCreatureTypes")) {
|
||||||
state.removeStaticAbility(sta);
|
state.removeStaticAbility(sta);
|
||||||
}
|
}
|
||||||
if ((cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) && sta.hasParam("SetColor")) {
|
if ((sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) && sta.hasParam("SetColor")) {
|
||||||
state.removeStaticAbility(sta);
|
state.removeStaticAbility(sta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove some keywords
|
// remove some keywords
|
||||||
if (cause.hasParam("SetCreatureTypes")) {
|
if (sa.hasParam("SetCreatureTypes")) {
|
||||||
state.removeIntrinsicKeyword(Keyword.CHANGELING);
|
state.removeIntrinsicKeyword(Keyword.CHANGELING);
|
||||||
}
|
}
|
||||||
if (cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) {
|
if (sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) {
|
||||||
state.removeIntrinsicKeyword(Keyword.DEVOID);
|
state.removeIntrinsicKeyword(Keyword.DEVOID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2238,7 +2238,7 @@ public class CardFactoryUtil {
|
|||||||
final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | "
|
final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | "
|
||||||
+ "Secondary$ True | Optional$ True | CheckSVar$ "
|
+ "Secondary$ True | Optional$ True | CheckSVar$ "
|
||||||
+ "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount
|
+ "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount
|
||||||
+ " | Description$ CARDNAME - Dredge " + dredgeAmount;
|
+ " | AICheckDredge$ True | Description$ CARDNAME - Dredge " + dredgeAmount;
|
||||||
|
|
||||||
final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount;
|
final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount;
|
||||||
|
|
||||||
@@ -2568,7 +2568,7 @@ public class CardFactoryUtil {
|
|||||||
} else if (keyword.equals("Sunburst")) {
|
} else if (keyword.equals("Sunburst")) {
|
||||||
// Rule 702.43a If this object is entering the battlefield as a creature,
|
// Rule 702.43a If this object is entering the battlefield as a creature,
|
||||||
// ignoring any type-changing effects that would affect it
|
// ignoring any type-changing effects that would affect it
|
||||||
CounterType t = host.isCreature() ? CounterEnumType.P1P1 : CounterEnumType.CHARGE;
|
CounterType t = CounterType.get(host.isCreature() ? CounterEnumType.P1P1 : CounterEnumType.CHARGE);
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder("etbCounter:");
|
StringBuilder sb = new StringBuilder("etbCounter:");
|
||||||
sb.append(t).append(":Sunburst:no Condition:");
|
sb.append(t).append(":Sunburst:no Condition:");
|
||||||
@@ -2776,9 +2776,10 @@ public class CardFactoryUtil {
|
|||||||
final String cost = params[1];
|
final String cost = params[1];
|
||||||
|
|
||||||
final StringBuilder sbAttach = new StringBuilder();
|
final StringBuilder sbAttach = new StringBuilder();
|
||||||
sbAttach.append("SP$ Attach | ValidTgts$ Creature | Cost$ ");
|
sbAttach.append("SP$ Attach | Cost$ ");
|
||||||
sbAttach.append(cost);
|
sbAttach.append(cost);
|
||||||
sbAttach.append(" | AILogic$ ").append(params.length > 2 ? params[2] : "Pump");
|
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 SpellAbility sa = AbilityFactory.getAbility(sbAttach.toString(), card);
|
||||||
final StringBuilder sbDesc = new StringBuilder();
|
final StringBuilder sbDesc = new StringBuilder();
|
||||||
@@ -4112,7 +4113,7 @@ public class CardFactoryUtil {
|
|||||||
sbValid.append("| ").append(param).append(k[1]);
|
sbValid.append("| ").append(param).append(k[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
String effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True"
|
String effect = "Mode$ CantTarget | ValidCard$ Card.Self | Secondary$ True"
|
||||||
+ sbValid.toString() + " | Activator$ Opponent | Description$ "
|
+ sbValid.toString() + " | Activator$ Opponent | Description$ "
|
||||||
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
|
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
|
||||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||||
@@ -4153,7 +4154,7 @@ public class CardFactoryUtil {
|
|||||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||||
|
|
||||||
// Target
|
// Target
|
||||||
effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True ";
|
effect = "Mode$ CantTarget | Protection$ True | ValidCard$ Card.Self | Secondary$ True ";
|
||||||
if (!valid.isEmpty()) {
|
if (!valid.isEmpty()) {
|
||||||
effect += "| ValidSource$ " + valid;
|
effect += "| ValidSource$ " + valid;
|
||||||
}
|
}
|
||||||
@@ -4161,7 +4162,7 @@ public class CardFactoryUtil {
|
|||||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||||
|
|
||||||
// Attach
|
// Attach
|
||||||
effect = "Mode$ CantAttach | Target$ Card.Self | Secondary$ True ";
|
effect = "Mode$ CantAttach | Protection$ True | Target$ Card.Self | Secondary$ True ";
|
||||||
if (!valid.isEmpty()) {
|
if (!valid.isEmpty()) {
|
||||||
effect += "| ValidCard$ " + valid;
|
effect += "| ValidCard$ " + valid;
|
||||||
}
|
}
|
||||||
@@ -4182,7 +4183,7 @@ public class CardFactoryUtil {
|
|||||||
" | Description$ Chapter abilities of this Saga can't trigger the turn it entered the battlefield unless it has exactly the number of lore counters on it specified in the chapter symbol of that ability.";
|
" | 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));
|
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||||
} else if (keyword.equals("Shroud")) {
|
} else if (keyword.equals("Shroud")) {
|
||||||
String effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True"
|
String effect = "Mode$ CantTarget | ValidCard$ Card.Self | Secondary$ True"
|
||||||
+ " | Description$ Shroud (" + inst.getReminderText() + ")";
|
+ " | Description$ Shroud (" + inst.getReminderText() + ")";
|
||||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||||
} else if (keyword.equals("Skulk")) {
|
} else if (keyword.equals("Skulk")) {
|
||||||
|
|||||||
@@ -26,17 +26,12 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.staticability.StaticAbilityTapPowerValue;
|
import forge.game.staticability.StaticAbilityTapPowerValue;
|
||||||
import forge.util.IterableUtil;
|
import forge.util.IterableUtil;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.StreamUtil;
|
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collector;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -485,26 +480,4 @@ public class CardLists {
|
|||||||
// (b) including the last element
|
// (b) including the last element
|
||||||
return isSubsetSum(numList, sum) || isSubsetSum(numList, sum - last);
|
return isSubsetSum(numList, sum) || isSubsetSum(numList, sum - last);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDifferentNamesCount(Iterable<Card> cardList) {
|
|
||||||
// first part the ones with SpyKit, and already collect them via
|
|
||||||
Map<Boolean, List<Card>> parted = StreamUtil.stream(cardList).collect(Collectors
|
|
||||||
.partitioningBy(Card::hasNonLegendaryCreatureNames, Collector.of(ArrayList::new, (list, c) -> {
|
|
||||||
if (!c.hasNoName() && list.stream().noneMatch(c2 -> c.sharesNameWith(c2))) {
|
|
||||||
list.add(c);
|
|
||||||
}
|
|
||||||
}, (l1, l2) -> {
|
|
||||||
l1.addAll(l2);
|
|
||||||
return l1;
|
|
||||||
})));
|
|
||||||
List<Card> preList = parted.get(Boolean.FALSE);
|
|
||||||
|
|
||||||
// then try to apply the SpyKit ones
|
|
||||||
for (Card c : parted.get(Boolean.TRUE)) {
|
|
||||||
if (preList.stream().noneMatch(c2 -> c.sharesNameWith(c2))) {
|
|
||||||
preList.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return preList.size();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,10 +213,16 @@ public final class CardPredicates {
|
|||||||
public static Predicate<Card> hasCounter(final CounterType type) {
|
public static Predicate<Card> hasCounter(final CounterType type) {
|
||||||
return hasCounter(type, 1);
|
return hasCounter(type, 1);
|
||||||
}
|
}
|
||||||
|
public static Predicate<Card> hasCounter(final CounterEnumType type) {
|
||||||
|
return hasCounter(type, 1);
|
||||||
|
}
|
||||||
|
|
||||||
public static Predicate<Card> hasCounter(final CounterType type, final int n) {
|
public static Predicate<Card> hasCounter(final CounterType type, final int n) {
|
||||||
return c -> c.getCounters(type) >= n;
|
return c -> c.getCounters(type) >= n;
|
||||||
}
|
}
|
||||||
|
public static Predicate<Card> hasCounter(final CounterEnumType type, final int n) {
|
||||||
|
return hasCounter(CounterType.get(type), n);
|
||||||
|
}
|
||||||
|
|
||||||
public static Predicate<Card> hasLessCounter(final CounterType type, final int n) {
|
public static Predicate<Card> hasLessCounter(final CounterType type, final int n) {
|
||||||
return c -> {
|
return c -> {
|
||||||
@@ -224,10 +230,16 @@ public final class CardPredicates {
|
|||||||
return x > 0 && x <= n;
|
return x > 0 && x <= n;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
public static Predicate<Card> hasLessCounter(final CounterEnumType type, final int n) {
|
||||||
|
return hasLessCounter(CounterType.get(type), n);
|
||||||
|
}
|
||||||
|
|
||||||
public static Predicate<Card> canReceiveCounters(final CounterType counter) {
|
public static Predicate<Card> canReceiveCounters(final CounterType counter) {
|
||||||
return c -> c.canReceiveCounters(counter);
|
return c -> c.canReceiveCounters(counter);
|
||||||
}
|
}
|
||||||
|
public static Predicate<Card> canReceiveCounters(final CounterEnumType counter) {
|
||||||
|
return canReceiveCounters(CounterType.get(counter));
|
||||||
|
}
|
||||||
|
|
||||||
public static Predicate<Card> hasGreaterPowerThan(final int minPower) {
|
public static Predicate<Card> hasGreaterPowerThan(final int minPower) {
|
||||||
return c -> c.getNetPower() > minPower;
|
return c -> c.getNetPower() > minPower;
|
||||||
@@ -236,6 +248,9 @@ public final class CardPredicates {
|
|||||||
public static Comparator<Card> compareByCounterType(final CounterType type) {
|
public static Comparator<Card> compareByCounterType(final CounterType type) {
|
||||||
return Comparator.comparingInt(arg0 -> arg0.getCounters(type));
|
return Comparator.comparingInt(arg0 -> arg0.getCounters(type));
|
||||||
}
|
}
|
||||||
|
public static Comparator<Card> compareByCounterType(final CounterEnumType type) {
|
||||||
|
return compareByCounterType(CounterType.get(type));
|
||||||
|
}
|
||||||
|
|
||||||
public static Predicate<Card> hasSVar(final String name) {
|
public static Predicate<Card> hasSVar(final String name) {
|
||||||
return c -> c.hasSVar(name);
|
return c -> c.hasSVar(name);
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ public class CardProperty {
|
|||||||
if (!card.sharesNameWith(name)) {
|
if (!card.sharesNameWith(name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("notnamed")) {
|
||||||
|
String name = TextUtil.fastReplace(property.substring(8), ";", ","); // workaround for card name with ","
|
||||||
|
name = TextUtil.fastReplace(name, "_", " ");
|
||||||
|
if (card.sharesNameWith(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (property.equals("NamedCard")) {
|
} else if (property.equals("NamedCard")) {
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (String name : source.getNamedCards()) {
|
for (String name : source.getNamedCards()) {
|
||||||
@@ -1238,8 +1244,7 @@ public class CardProperty {
|
|||||||
if (property.contains("ControlledBy")) {
|
if (property.contains("ControlledBy")) {
|
||||||
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], spellAbility);
|
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], spellAbility);
|
||||||
cards = CardLists.filterControlledBy(cards, p);
|
cards = CardLists.filterControlledBy(cards, p);
|
||||||
// Kraven the Hunter LTB trigger
|
if (!cards.contains(card)) {
|
||||||
if (!card.isLKI() && !cards.contains(card)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1558,6 +1563,8 @@ public class CardProperty {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("notattacking")) {
|
||||||
|
return null == combat || !combat.isAttacking(card);
|
||||||
} else if (property.startsWith("enlistedThisCombat")) {
|
} else if (property.startsWith("enlistedThisCombat")) {
|
||||||
if (card.getEnlistedThisCombat() == false) return false;
|
if (card.getEnlistedThisCombat() == false) return false;
|
||||||
} else if (property.startsWith("attackedThisCombat")) {
|
} else if (property.startsWith("attackedThisCombat")) {
|
||||||
@@ -1611,6 +1618,8 @@ public class CardProperty {
|
|||||||
if (Collections.disjoint(combat.getAttackersBlockedBy(source), combat.getAttackersBlockedBy(card))) {
|
if (Collections.disjoint(combat.getAttackersBlockedBy(source), combat.getAttackersBlockedBy(card))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("notblocking")) {
|
||||||
|
return null == combat || !combat.isBlocking(card);
|
||||||
}
|
}
|
||||||
// Nex predicates refer to past combat and don't need a reference to actual combat
|
// Nex predicates refer to past combat and don't need a reference to actual combat
|
||||||
else if (property.equals("blocked")) {
|
else if (property.equals("blocked")) {
|
||||||
@@ -1810,10 +1819,6 @@ public class CardProperty {
|
|||||||
if (!card.isWarped()) {
|
if (!card.isWarped()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (property.equals("webSlinged")) {
|
|
||||||
if (!card.isWebSlinged()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (property.equals("CrewedThisTurn")) {
|
} else if (property.equals("CrewedThisTurn")) {
|
||||||
if (!hasTimestampMatch(card, source.getCrewedByThisTurn())) return false;
|
if (!hasTimestampMatch(card, source.getCrewedByThisTurn())) return false;
|
||||||
} else if (property.equals("CrewedBySourceThisTurn")) {
|
} else if (property.equals("CrewedBySourceThisTurn")) {
|
||||||
@@ -1822,10 +1827,6 @@ public class CardProperty {
|
|||||||
if (card.getDevouredCards().isEmpty()) {
|
if (card.getDevouredCards().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (property.equals("harnessed")) {
|
|
||||||
if (!card.isHarnessed()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (property.equals("IsMonstrous")) {
|
} else if (property.equals("IsMonstrous")) {
|
||||||
if (!card.isMonstrous()) {
|
if (!card.isMonstrous()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2063,6 +2064,16 @@ public class CardProperty {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("NotTriggered")) {
|
||||||
|
final String key = property.substring("NotTriggered".length());
|
||||||
|
if (spellAbility instanceof SpellAbility) {
|
||||||
|
SpellAbility sa = (SpellAbility) spellAbility;
|
||||||
|
if (card.equals(sa.getRootAbility().getTriggeringObject(AbilityKey.fromString(key)))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (property.startsWith("NotDefined")) {
|
} else if (property.startsWith("NotDefined")) {
|
||||||
final String key = property.substring("NotDefined".length());
|
final String key = property.substring("NotDefined".length());
|
||||||
if (AbilityUtils.getDefinedCards(source, key, spellAbility).contains(card)) {
|
if (AbilityUtils.getDefinedCards(source, key, spellAbility).contains(card)) {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import forge.game.card.CardView.CardStateView;
|
|||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordCollection;
|
import forge.game.keyword.KeywordCollection;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.keyword.KeywordWithType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.spellability.LandAbility;
|
import forge.game.spellability.LandAbility;
|
||||||
@@ -41,7 +40,6 @@ import forge.game.spellability.SpellAbilityPredicates;
|
|||||||
import forge.game.spellability.SpellPermanent;
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.util.CardTranslation;
|
|
||||||
import forge.util.ITranslatable;
|
import forge.util.ITranslatable;
|
||||||
import forge.util.IterableUtil;
|
import forge.util.IterableUtil;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
@@ -369,7 +367,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
public final FCollectionView<SpellAbility> getManaAbilities() {
|
public final FCollectionView<SpellAbility> getManaAbilities() {
|
||||||
FCollection<SpellAbility> newCol = new FCollection<>();
|
FCollection<SpellAbility> newCol = new FCollection<>();
|
||||||
updateSpellAbilities(newCol, true);
|
updateSpellAbilities(newCol, true);
|
||||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||||
newCol.addAll(abilities.stream().filter(SpellAbility::isManaAbility).collect(Collectors.toList()));
|
newCol.addAll(abilities.stream().filter(SpellAbility::isManaAbility).collect(Collectors.toList()));
|
||||||
card.updateSpellAbilities(newCol, this, true);
|
card.updateSpellAbilities(newCol, this, true);
|
||||||
return newCol;
|
return newCol;
|
||||||
@@ -377,7 +375,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
public final FCollectionView<SpellAbility> getNonManaAbilities() {
|
public final FCollectionView<SpellAbility> getNonManaAbilities() {
|
||||||
FCollection<SpellAbility> newCol = new FCollection<>();
|
FCollection<SpellAbility> newCol = new FCollection<>();
|
||||||
updateSpellAbilities(newCol, false);
|
updateSpellAbilities(newCol, false);
|
||||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||||
newCol.addAll(abilities.stream().filter(Predicate.not(SpellAbility::isManaAbility)).collect(Collectors.toList()));
|
newCol.addAll(abilities.stream().filter(Predicate.not(SpellAbility::isManaAbility)).collect(Collectors.toList()));
|
||||||
card.updateSpellAbilities(newCol, this, false);
|
card.updateSpellAbilities(newCol, this, false);
|
||||||
return newCol;
|
return newCol;
|
||||||
@@ -392,7 +390,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
if (null != mana) {
|
if (null != mana) {
|
||||||
leftAbilities = leftAbilities.stream()
|
leftAbilities = leftAbilities.stream()
|
||||||
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
|
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
|
||||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
newCol.addAll(leftAbilities);
|
newCol.addAll(leftAbilities);
|
||||||
@@ -404,7 +402,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
if (null != mana) {
|
if (null != mana) {
|
||||||
rightAbilities = rightAbilities.stream()
|
rightAbilities = rightAbilities.stream()
|
||||||
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
|
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
|
||||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
newCol.addAll(rightAbilities);
|
newCol.addAll(rightAbilities);
|
||||||
@@ -470,9 +468,6 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
return Iterables.getFirst(getIntrinsicSpellAbilities(), null);
|
return Iterables.getFirst(getIntrinsicSpellAbilities(), null);
|
||||||
}
|
}
|
||||||
public final SpellAbility getFirstSpellAbility() {
|
public final SpellAbility getFirstSpellAbility() {
|
||||||
if (this.card.getCastSA() != null) {
|
|
||||||
return this.card.getCastSA();
|
|
||||||
}
|
|
||||||
return Iterables.getFirst(getNonManaAbilities(), null);
|
return Iterables.getFirst(getNonManaAbilities(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,8 +497,15 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
String desc = "";
|
String desc = "";
|
||||||
String extra = "";
|
String extra = "";
|
||||||
for (KeywordInterface ki : this.getCachedKeyword(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : this.getCachedKeyword(Keyword.ENCHANT)) {
|
||||||
if (ki instanceof KeywordWithType kwt) {
|
String o = ki.getOriginal();
|
||||||
desc = kwt.getTypeDescription();
|
String m[] = o.split(":");
|
||||||
|
if (m.length > 2) {
|
||||||
|
desc = m[2];
|
||||||
|
} else {
|
||||||
|
desc = m[1];
|
||||||
|
if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) {
|
||||||
|
desc = desc.toLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -603,18 +605,18 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
result.add(loyaltyRep);
|
result.add(loyaltyRep);
|
||||||
}
|
}
|
||||||
if (type.isBattle()) {
|
if (type.isBattle()) {
|
||||||
|
// TODO This is currently breaking for Battle/Defense
|
||||||
|
// Going to script the cards to work but ideally it would happen here
|
||||||
if (defenseRep == null) {
|
if (defenseRep == null) {
|
||||||
defenseRep = CardFactoryUtil.makeEtbCounter("etbCounter:DEFENSE:" + this.baseDefense, this, true);
|
defenseRep = CardFactoryUtil.makeEtbCounter("etbCounter:DEFENSE:" + this.baseDefense, this, true);
|
||||||
}
|
}
|
||||||
result.add(defenseRep);
|
result.add(defenseRep);
|
||||||
|
|
||||||
|
// TODO add Siege "Choose a player to protect it"
|
||||||
}
|
}
|
||||||
|
|
||||||
card.updateReplacementEffects(result, this);
|
|
||||||
|
|
||||||
// below are global rules
|
|
||||||
if (type.hasSubtype("Saga") && !hasKeyword(Keyword.READ_AHEAD)) {
|
if (type.hasSubtype("Saga") && !hasKeyword(Keyword.READ_AHEAD)) {
|
||||||
if (sagaRep == null) {
|
if (sagaRep == null) {
|
||||||
sagaRep = CardFactoryUtil.makeEtbCounter("etbCounter:LORE:1", this, false);
|
sagaRep = CardFactoryUtil.makeEtbCounter("etbCounter:LORE:1", this, true);
|
||||||
}
|
}
|
||||||
result.add(sagaRep);
|
result.add(sagaRep);
|
||||||
}
|
}
|
||||||
@@ -631,6 +633,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
result.add(omenRep);
|
result.add(omenRep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
card.updateReplacementEffects(result, this);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
public boolean addReplacementEffect(final ReplacementEffect replacementEffect) {
|
public boolean addReplacementEffect(final ReplacementEffect replacementEffect) {
|
||||||
@@ -941,7 +944,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTranslatedName() {
|
public String getUntranslatedOracle() {
|
||||||
return CardTranslation.getTranslatedName(this);
|
return getOracleText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user