mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 16:26:22 +00:00
Merge branch 'master' into setEventCommand
# Conflicts: # forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java # forge-gui-mobile/src/forge/adventure/stage/MapStage.java # forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -4,6 +4,7 @@ about: Create a report to help us improve
|
|||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
type: 'Bug'
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -31,7 +32,6 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
**Smartphone (please complete the following information):**
|
**Smartphone (please complete the following information):**
|
||||||
- Device: [e.g. iPhone6]
|
- Device: [e.g. iPhone6]
|
||||||
- OS: [e.g. iOS8.1]
|
- OS: [e.g. iOS8.1]
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
- Version [e.g. 22]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -4,6 +4,7 @@ about: Suggest an idea for this project
|
|||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
type: 'Feature'
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/maven-publish.yml
vendored
4
.github/workflows/maven-publish.yml
vendored
@@ -129,7 +129,9 @@ jobs:
|
|||||||
makeLatest: true
|
makeLatest: true
|
||||||
|
|
||||||
- name: 🔧 Install XML tools
|
- name: 🔧 Install XML tools
|
||||||
run: sudo apt-get install -y libxml2-utils
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libxml2-utils
|
||||||
|
|
||||||
- name: 🔼 Bump versionCode in root POM
|
- 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' ]
|
java: ['17', '21']
|
||||||
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,6 +66,9 @@ 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
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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);
|
GuiBase.setDeviceInfo(null, 0, 0, System.getProperty("user.home") + "/Downloads/");
|
||||||
new EditorMainWindow(Config.instance());
|
new EditorMainWindow(Config.instance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ 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;
|
||||||
@@ -886,27 +887,8 @@ 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();
|
||||||
|
|
||||||
// Check a predefined condition
|
if (sa.hasParam("AICheckSVar") && !aiShouldRun(sa, sa, host, null)) {
|
||||||
if (sa.hasParam("AICheckSVar")) {
|
return AiPlayDecision.AnotherTime;
|
||||||
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.
|
||||||
@@ -924,7 +906,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;
|
||||||
}
|
}
|
||||||
@@ -1664,6 +1646,9 @@ 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);
|
||||||
@@ -1673,6 +1658,11 @@ 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(
|
||||||
@@ -1752,7 +1742,10 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -1805,14 +1798,9 @@ public class AiController {
|
|||||||
* @param sa the sa
|
* @param sa the sa
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) {
|
public final boolean aiShouldRun(final CardTraitBase effect, final SpellAbility sa, final Card host, final 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 = hostCard.getController();
|
final Player controller = host.getController();
|
||||||
if (affected instanceof Player) {
|
if (affected instanceof Player) {
|
||||||
return !((Player) affected).isOpponentOf(controller);
|
return !((Player) affected).isOpponentOf(controller);
|
||||||
}
|
}
|
||||||
@@ -1821,7 +1809,6 @@ 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;
|
||||||
@@ -1834,9 +1821,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(hostCard, hostCard.getSVar(strCmpTo), effect);
|
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), effect);
|
||||||
} else {
|
} else {
|
||||||
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1844,13 +1831,12 @@ public class AiController {
|
|||||||
int left = 0;
|
int left = 0;
|
||||||
|
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
|
left = AbilityUtils.calculateAmount(host, svarToCheck, effect);
|
||||||
} else {
|
} else {
|
||||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
|
||||||
}
|
}
|
||||||
System.out.println("aiShouldRun?" + left + comparator + compareTo);
|
|
||||||
return Expressions.compare(left, comparator, compareTo);
|
return Expressions.compare(left, comparator, compareTo);
|
||||||
} else if (effect.hasParam("AICheckDredge")) {
|
} else if (effect.isKeyword(Keyword.DREDGE)) {
|
||||||
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,12 +29,15 @@ 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 (tappedForMana != null) {
|
if (!payMana && tappedForMana != null) {
|
||||||
tapped.addAll(tappedForMana);
|
tapped.addAll(tappedForMana);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +113,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.equals("DifferentNames")) {
|
} else if (type.contains("+WithDifferentNames")) {
|
||||||
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) {
|
||||||
@@ -563,7 +566,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, CounterType.get(cType), thisRemove);
|
table.put(null, prefCard, cType, thisRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,7 +576,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.defaultIfNull(ability.getOriginalHost(), source);
|
final Card originalHost = ObjectUtils.getIfNull(ability.getOriginalHost(), source);
|
||||||
|
|
||||||
if (c <= 0) {
|
if (c <= 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -716,7 +719,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, CounterType.get(CounterEnumType.QUEST), over);
|
table.put(null, crd, 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(CounterType.get(CounterEnumType.STUN)));
|
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(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 = CounterType.get(CounterEnumType.P1P1);
|
final CounterType p1p1Type = CounterEnumType.P1P1;
|
||||||
|
|
||||||
if (!sa.hasParam("AILogic")) {
|
if (!sa.hasParam("AILogic")) {
|
||||||
return Aggregates.random(options);
|
return Aggregates.random(options);
|
||||||
@@ -3104,41 +3104,38 @@ 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) { return srcList; }
|
if (source == null || !sa.hasParam("AITgts")) {
|
||||||
|
return srcList;
|
||||||
if (sa.hasParam("AITgts")) {
|
|
||||||
CardCollection list;
|
|
||||||
String aiTgts = sa.getParam("AITgts");
|
|
||||||
if (aiTgts.startsWith("BetterThan")) {
|
|
||||||
int value = 0;
|
|
||||||
if (aiTgts.endsWith("Source")) {
|
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
|
||||||
if (source.isEnchanted()) {
|
|
||||||
for (Card enc : source.getEnchantedBy()) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
return srcList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CardCollection list;
|
||||||
|
String aiTgts = sa.getParam("AITgts");
|
||||||
|
if (aiTgts.startsWith("BetterThan")) {
|
||||||
|
int value = 0;
|
||||||
|
if (aiTgts.endsWith("Source")) {
|
||||||
|
value = ComputerUtilCard.evaluateCreature(source);
|
||||||
|
if (source.isEnchanted()) {
|
||||||
|
for (Card enc : source.getEnchantedBy()) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,16 +177,16 @@ public class ComputerUtilCombat {
|
|||||||
public static int damageIfUnblocked(final Card attacker, final GameEntity attacked, final Combat combat, boolean withoutAbilities) {
|
public static int damageIfUnblocked(final Card attacker, final GameEntity attacked, final Combat combat, boolean withoutAbilities) {
|
||||||
int damage = attacker.getNetCombatDamage();
|
int damage = attacker.getNetCombatDamage();
|
||||||
int sum = 0;
|
int sum = 0;
|
||||||
if (attacked instanceof Player player && !player.canLoseLife()) {
|
if (attacked instanceof Player p && !p.canLoseLife()) {
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ask ReplacementDamage directly
|
|
||||||
if (isCombatDamagePrevented(attacker, attacked, damage)) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!attacker.hasKeyword(Keyword.INFECT)) {
|
if (!attacker.hasKeyword(Keyword.INFECT)) {
|
||||||
|
// ask ReplacementDamage directly
|
||||||
|
if (isCombatDamagePrevented(attacker, attacked, damage)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
damage += predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
|
damage += predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
|
||||||
sum = predictDamageTo(attacked, damage, attacker, true);
|
sum = predictDamageTo(attacked, damage, attacker, true);
|
||||||
if (attacker.hasDoubleStrike()) {
|
if (attacker.hasDoubleStrike()) {
|
||||||
@@ -974,17 +974,13 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, blocker.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")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -998,12 +994,11 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, blocker.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, blocker.getController(), false)) {
|
||||||
}
|
power += pBonus;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1107,17 +1102,13 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||||
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;
|
||||||
@@ -1131,12 +1122,11 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
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, blocker.getController(), false)) {
|
||||||
}
|
toughness += tBonus;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toughness;
|
return toughness;
|
||||||
@@ -1305,6 +1295,7 @@ 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;
|
||||||
@@ -1314,11 +1305,8 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
if (!ability.getPayCosts().hasTapCost()) {
|
||||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
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")) {
|
||||||
@@ -1333,13 +1321,14 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
if (!ability.getPayCosts().hasTapCost()) {
|
||||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
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;
|
||||||
}
|
}
|
||||||
@@ -1530,16 +1519,14 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
toughness += AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} 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;
|
||||||
@@ -1553,10 +1540,11 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
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;
|
||||||
@@ -2020,35 +2008,35 @@ public class ComputerUtilCombat {
|
|||||||
*
|
*
|
||||||
* @param self
|
* @param self
|
||||||
* a {@link forge.game.player.Player} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
* @param attacker
|
* @param combatant
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param block
|
* @param opposedCombatants
|
||||||
* @param dmgCanDeal
|
* @param dmgCanDeal
|
||||||
* a int.
|
* a int.
|
||||||
* @param defender
|
* @param defender
|
||||||
* @param overrideOrder overriding combatant order
|
* @param overrideOrder overriding combatant order
|
||||||
*/
|
*/
|
||||||
public static Map<Card, Integer> distributeAIDamage(final Player self, final Card attacker, final CardCollectionView block, final CardCollectionView remaining, int dmgCanDeal, GameEntity defender, boolean overrideOrder) {
|
public static Map<Card, Integer> distributeAIDamage(final Player self, final Card combatant, CardCollectionView opposedCombatants, final CardCollectionView remaining, int dmgCanDeal, GameEntity defender, boolean overrideOrder) {
|
||||||
Map<Card, Integer> damageMap = Maps.newHashMap();
|
Map<Card, Integer> damageMap = Maps.newHashMap();
|
||||||
Combat combat = attacker.getGame().getCombat();
|
Combat combat = combatant.getGame().getCombat();
|
||||||
|
|
||||||
boolean isAttacking = defender != null;
|
boolean isAttacking = defender != null;
|
||||||
|
|
||||||
// Check for Banding, Defensive Formation
|
// Check for Banding, Defensive Formation
|
||||||
boolean isAttackingMe = isAttacking && combat.getDefenderPlayerByAttacker(attacker).equals(self);
|
boolean isAttackingMe = isAttacking && combat.getDefenderPlayerByAttacker(combatant).equals(self);
|
||||||
boolean isBlockingMyBand = attacker.getController().isOpponentOf(self) && AttackingBand.isValidBand(block, true);
|
boolean isBlockingMyBand = combatant.getController().isOpponentOf(self) && AttackingBand.isValidBand(opposedCombatants, true);
|
||||||
final boolean aiDistributesBandingDmg = isAttackingMe || isBlockingMyBand;
|
final boolean aiDistributesBandingDmg = isAttackingMe || isBlockingMyBand;
|
||||||
|
|
||||||
final boolean hasTrample = attacker.hasKeyword(Keyword.TRAMPLE);
|
final boolean hasTrample = combatant.hasKeyword(Keyword.TRAMPLE);
|
||||||
|
|
||||||
if (combat != null && remaining != null && hasTrample && attacker.isAttacking() && !aiDistributesBandingDmg) {
|
if (combat != null && remaining != null && hasTrample && combatant.isAttacking() && !aiDistributesBandingDmg) {
|
||||||
// if attacker has trample and some of its blockers are also blocking others it's generally a good idea
|
// if attacker has trample and some of its blockers are also blocking others it's generally a good idea
|
||||||
// to assign those without trample first so we can maximize the damage to the defender
|
// to assign those without trample first so we can maximize the damage to the defender
|
||||||
for (final Card c : remaining) {
|
for (final Card c : remaining) {
|
||||||
if (c == attacker || c.hasKeyword(Keyword.TRAMPLE)) {
|
if (c == combatant || c.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final CardCollection sharedBlockers = new CardCollection(block);
|
final CardCollection sharedBlockers = new CardCollection(opposedCombatants);
|
||||||
sharedBlockers.retainAll(combat.getBlockers(c));
|
sharedBlockers.retainAll(combat.getBlockers(c));
|
||||||
if (!sharedBlockers.isEmpty()) {
|
if (!sharedBlockers.isEmpty()) {
|
||||||
// signal skip for now
|
// signal skip for now
|
||||||
@@ -2058,12 +2046,21 @@ public class ComputerUtilCombat {
|
|||||||
// TODO sort remaining tramplers for DamageDone triggers
|
// TODO sort remaining tramplers for DamageDone triggers
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.size() == 1) {
|
// Order the combatants in preferred order in case legacy ordering is disabled
|
||||||
final Card blocker = block.getFirst();
|
if (!self.getGame().getRules().hasOrderCombatants()) {
|
||||||
|
if (combatant.isAttacking()) {
|
||||||
|
opposedCombatants = AiBlockController.orderBlockers(combatant, new CardCollection(opposedCombatants));
|
||||||
|
} else {
|
||||||
|
opposedCombatants = AiBlockController.orderAttackers(combatant, new CardCollection(opposedCombatants));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opposedCombatants.size() == 1) {
|
||||||
|
final Card blocker = opposedCombatants.getFirst();
|
||||||
int dmgToBlocker = dmgCanDeal;
|
int dmgToBlocker = dmgCanDeal;
|
||||||
|
|
||||||
if (hasTrample && isAttacking && !aiDistributesBandingDmg) { // otherwise no entity to deliver damage via trample
|
if (hasTrample && isAttacking && !aiDistributesBandingDmg) { // otherwise no entity to deliver damage via trample
|
||||||
dmgToBlocker = getEnoughDamageToKill(blocker, dmgCanDeal, attacker, true);
|
dmgToBlocker = getEnoughDamageToKill(blocker, dmgCanDeal, combatant, true);
|
||||||
|
|
||||||
if (dmgCanDeal < dmgToBlocker) {
|
if (dmgCanDeal < dmgToBlocker) {
|
||||||
// can't kill so just put the lowest legal amount
|
// can't kill so just put the lowest legal amount
|
||||||
@@ -2082,9 +2079,9 @@ public class ComputerUtilCombat {
|
|||||||
// Does the attacker deal lethal damage to all blockers
|
// Does the attacker deal lethal damage to all blockers
|
||||||
//Blocking Order now determined after declare blockers
|
//Blocking Order now determined after declare blockers
|
||||||
Card lastBlocker = null;
|
Card lastBlocker = null;
|
||||||
for (final Card b : block) {
|
for (final Card b : opposedCombatants) {
|
||||||
lastBlocker = b;
|
lastBlocker = b;
|
||||||
final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true);
|
final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, combatant, true);
|
||||||
if (dmgToKill <= dmgCanDeal) {
|
if (dmgToKill <= dmgCanDeal) {
|
||||||
damageMap.put(b, dmgToKill);
|
damageMap.put(b, dmgToKill);
|
||||||
dmgCanDeal -= dmgToKill;
|
dmgCanDeal -= dmgToKill;
|
||||||
@@ -2109,15 +2106,15 @@ public class ComputerUtilCombat {
|
|||||||
} else {
|
} else {
|
||||||
// In the event of Banding or Defensive Formation, assign max damage to the blocker who
|
// In the event of Banding or Defensive Formation, assign max damage to the blocker who
|
||||||
// can tank all the damage or to the worst blocker to lose as little as possible
|
// can tank all the damage or to the worst blocker to lose as little as possible
|
||||||
for (final Card b : block) {
|
for (final Card b : opposedCombatants) {
|
||||||
final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true);
|
final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, combatant, true);
|
||||||
if (dmgToKill > dmgCanDeal) {
|
if (dmgToKill > dmgCanDeal) {
|
||||||
damageMap.put(b, dmgCanDeal);
|
damageMap.put(b, dmgCanDeal);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (damageMap.isEmpty()) {
|
if (damageMap.isEmpty()) {
|
||||||
damageMap.put(ComputerUtilCard.getWorstCreatureAI(block), dmgCanDeal);
|
damageMap.put(ComputerUtilCard.getWorstCreatureAI(opposedCombatants), dmgCanDeal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return damageMap;
|
return damageMap;
|
||||||
|
|||||||
@@ -287,7 +287,9 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) {
|
int amount = ma.hasParam("Amount") ? AbilityUtils.calculateAmount(ma.getHostCard(), ma.getParam("Amount"), ma) : 1;
|
||||||
|
if (amount <= 0) {
|
||||||
|
// wrong gamestate for variable amount
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,9 +353,14 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -443,7 +450,6 @@ 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()) {
|
||||||
@@ -704,9 +710,9 @@ public class ComputerUtilMana {
|
|||||||
if (hasConverge &&
|
if (hasConverge &&
|
||||||
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
||||||
final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
|
final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
|
||||||
for (final byte b : ColorSet.fromMask(unpaidColors)) {
|
for (final MagicColor.Color b : ColorSet.fromMask(unpaidColors)) {
|
||||||
// try and pay other colors for converge
|
// try and pay other colors for converge
|
||||||
final ManaCostShard shard = ManaCostShard.valueOf(b);
|
final ManaCostShard shard = ManaCostShard.valueOf(b.getColorMask());
|
||||||
saList = sourcesForShards.get(shard);
|
saList = sourcesForShards.get(shard);
|
||||||
if (saList != null && !saList.isEmpty()) {
|
if (saList != null && !saList.isEmpty()) {
|
||||||
toPay = shard;
|
toPay = shard;
|
||||||
@@ -735,7 +741,8 @@ 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())) {
|
||||||
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
|
// not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||||
|
saExcludeList.add(saPayment);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -809,11 +816,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 from available lists
|
// remove to prevent re-usage since resources don't get consumed
|
||||||
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))) {
|
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect, true))) {
|
||||||
saList.remove(saPayment);
|
saList.remove(saPayment);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -822,8 +829,10 @@ public class ComputerUtilMana {
|
|||||||
// subtract mana from mana pool
|
// subtract mana from mana pool
|
||||||
manapool.payManaFromAbility(sa, cost, saPayment);
|
manapool.payManaFromAbility(sa, cost, saPayment);
|
||||||
|
|
||||||
// no need to remove abilities from resource map,
|
// need to consider if another use is now prevented
|
||||||
// once their costs are paid and consume resources, they can not be used again
|
if (!cost.isPaid() && saPayment.isActivatedAbility() && !saPayment.getRestrictions().canPlay(saPayment.getHostCard(), saPayment)) {
|
||||||
|
sourcesForShards.values().removeIf(s -> s == saPayment);
|
||||||
|
}
|
||||||
|
|
||||||
if (hasConverge) {
|
if (hasConverge) {
|
||||||
// hack to prevent converge re-using sources
|
// hack to prevent converge re-using sources
|
||||||
@@ -887,7 +896,8 @@ public class ComputerUtilMana {
|
|||||||
if (hasConverge) {
|
if (hasConverge) {
|
||||||
// add extra colors for paying converge
|
// add extra colors for paying converge
|
||||||
final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
|
final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
|
||||||
for (final byte b : ColorSet.fromMask(unpaidColors)) {
|
for (final MagicColor.Color color : ColorSet.fromMask(unpaidColors)) {
|
||||||
|
final byte b = color.getColorMask();
|
||||||
final ManaCostShard shard = ManaCostShard.valueOf(b);
|
final ManaCostShard shard = ManaCostShard.valueOf(b);
|
||||||
if (!sourcesForShards.containsKey(shard)) {
|
if (!sourcesForShards.containsKey(shard)) {
|
||||||
if (ai.getManaPool().canPayForShardWithColor(shard, b)) {
|
if (ai.getManaPool().canPayForShardWithColor(shard, b)) {
|
||||||
@@ -914,7 +924,7 @@ public class ComputerUtilMana {
|
|||||||
ColorSet shared = ColorSet.fromMask(toPay.getColorMask()).getSharedColors(ColorSet.fromNames(m.getComboColors(saPayment).split(" ")));
|
ColorSet shared = ColorSet.fromMask(toPay.getColorMask()).getSharedColors(ColorSet.fromNames(m.getComboColors(saPayment).split(" ")));
|
||||||
// but other effects might still lead to a more permissive payment
|
// but other effects might still lead to a more permissive payment
|
||||||
if (!shared.isColorless()) {
|
if (!shared.isColorless()) {
|
||||||
m.setExpressChoice(ColorSet.fromMask(shared.iterator().next()));
|
m.setExpressChoice(shared.iterator().next().getShortName());
|
||||||
}
|
}
|
||||||
getComboManaChoice(ai, saPayment, sa, cost);
|
getComboManaChoice(ai, saPayment, sa, cost);
|
||||||
}
|
}
|
||||||
@@ -1089,7 +1099,7 @@ public class ComputerUtilMana {
|
|||||||
// * pay hybrids
|
// * pay hybrids
|
||||||
// * pay phyrexian, keep mana for colorless
|
// * pay phyrexian, keep mana for colorless
|
||||||
// * pay generic
|
// * pay generic
|
||||||
return cost.getShardToPayByPriority(shardsToPay, ColorSet.ALL_COLORS.getColor());
|
return cost.getShardToPayByPriority(shardsToPay, ColorSet.WUBRG.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void adjustManaCostToAvoidNegEffects(ManaCostBeingPaid cost, final Card card, Player ai) {
|
private static void adjustManaCostToAvoidNegEffects(ManaCostBeingPaid cost, final Card card, Player ai) {
|
||||||
@@ -1496,7 +1506,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;
|
||||||
}
|
}
|
||||||
@@ -1587,10 +1597,8 @@ 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) {
|
if (sub != null && !SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||||
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
||||||
@@ -1658,7 +1666,6 @@ public class ComputerUtilMana {
|
|||||||
if (replaced.contains("C")) {
|
if (replaced.contains("C")) {
|
||||||
manaMap.put(ManaAtom.COLORLESS, m);
|
manaMap.put(ManaAtom.COLORLESS, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,7 +460,11 @@ 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) {
|
||||||
return brains.aiShouldRun(replacementEffect, effectSA, affected);
|
Card host = replacementEffect.getHostCard();
|
||||||
|
if (host.hasAlternateState()) {
|
||||||
|
host = host.getGame().getCardState(host);
|
||||||
|
}
|
||||||
|
return brains.aiShouldRun(replacementEffect, effectSA, host, affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1024,13 +1028,13 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if ((colors.getColor() & chosenColorMask) != 0) {
|
if ((colors.getColor() & chosenColorMask) != 0) {
|
||||||
return chosenColorMask;
|
return chosenColorMask;
|
||||||
}
|
}
|
||||||
return Iterables.getFirst(colors, (byte)0);
|
return Iterables.getFirst(colors, MagicColor.Color.COLORLESS).getColorMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
|
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
|
||||||
if (colors.countColors() < 2) {
|
if (colors.countColors() < 2) {
|
||||||
return Iterables.getFirst(colors, MagicColor.WHITE);
|
return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask();
|
||||||
}
|
}
|
||||||
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
|
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
|
||||||
CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
||||||
@@ -1043,7 +1047,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if ((colors.getColor() & chosenColorMask) != 0) {
|
if ((colors.getColor() & chosenColorMask) != 0) {
|
||||||
return chosenColorMask;
|
return chosenColorMask;
|
||||||
}
|
}
|
||||||
return Iterables.getFirst(colors, MagicColor.WHITE);
|
return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1347,6 +1351,11 @@ 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(CounterType.get(CounterEnumType.POISON))) {
|
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(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(CounterType.get(CounterEnumType.POISON))) {
|
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(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,11 +101,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiLogic.equals("BeforeCombat")) {
|
if (aiLogic.equals("SurpriseBlock")) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -298,13 +294,7 @@ 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")) {
|
||||||
try {
|
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||||
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");
|
||||||
|
|
||||||
@@ -771,6 +761,8 @@ 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()) {
|
||||||
@@ -897,9 +889,6 @@ 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.
|
||||||
@@ -908,8 +897,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
list.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)) {
|
||||||
@@ -1252,53 +1239,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if max CMC exceeded, do not choose this card (but keep looking for other options)
|
|
||||||
if (sa.hasParam("MaxTotalTargetCMC")) {
|
|
||||||
if (choice.getCMC() > sa.getTargetRestrictions().getMaxTotalCMC(choice, sa) - sa.getTargets().getTotalTargetedCMC()) {
|
|
||||||
list.remove(choice);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if max power exceeded, do not choose this card (but keep looking for other options)
|
|
||||||
if (sa.hasParam("MaxTotalTargetPower")) {
|
|
||||||
if (choice.getNetPower() > sa.getTargetRestrictions().getMaxTotalPower(choice, sa) -sa.getTargets().getTotalTargetedPower()) {
|
|
||||||
list.remove(choice);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// honor the Same Creature Type restriction
|
|
||||||
if (sa.getTargetRestrictions().isWithSameCreatureType()) {
|
|
||||||
Card firstTarget = sa.getTargetCard();
|
|
||||||
if (firstTarget != null && !choice.sharesCreatureTypeWith(firstTarget)) {
|
|
||||||
list.remove(choice);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.remove(choice);
|
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,6 +96,10 @@ 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);
|
||||||
@@ -188,7 +192,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+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name;
|
: "Permanent.YouDontCtrl+!named" + name + ",Permanent.nonLegendary+!named" + 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() && !sa.getTargets().isEmpty() ? 100 : 0,
|
sa.isTargetNumberValid() ? 100 : 0,
|
||||||
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,17 +53,15 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (mandatory) {
|
||||||
if (mandatory) {
|
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
||||||
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
if (sa.isTargetNumberValid()) {
|
||||||
if (sa.isTargetNumberValid()) {
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
return decision;
|
|
||||||
} else {
|
|
||||||
return canPlay(aiPlayer, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
} if ("DuplicatePerms".equals(aiLogic)) {
|
} else 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.TargetingFailed);
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,9 +135,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
if ("Never".equals(logic)) {
|
if (logic.startsWith("MinCMC.")) { // TODO fix Daze and fold into AITgts
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
} else if (logic.startsWith("MinCMC.")) { // TODO fix Daze and fold into AITgts
|
|
||||||
int minCMC = Integer.parseInt(logic.substring(7));
|
int minCMC = Integer.parseInt(logic.substring(7));
|
||||||
if (tgtCMC < minCMC) {
|
if (tgtCMC < minCMC) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|||||||
@@ -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.get(type).isKeywordCounter()) {
|
} else if (CounterType.getType(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.
|
||||||
|
|||||||
@@ -424,16 +424,20 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// move counter to opponents creature but only if you can not steal them
|
// move counter to opponents creature but only if you can not steal them
|
||||||
// try to move to something useless or something that would leave play
|
// try to move to something useless or something that would leave play
|
||||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
boolean isNegative = ComputerUtil.isNegativeCounter(cType, src);
|
||||||
if (!oppList.isEmpty()) {
|
List<Card> filteredTgtList;
|
||||||
List<Card> best = CardLists.filter(oppList, card -> {
|
filteredTgtList = isNegative ? CardLists.filterControlledBy(tgtCards, ai.getOpponents()) :
|
||||||
|
CardLists.filterControlledBy(tgtCards, ai.getYourTeam());
|
||||||
|
|
||||||
|
if (!filteredTgtList.isEmpty()) {
|
||||||
|
List<Card> best = CardLists.filter(filteredTgtList, card -> {
|
||||||
// gain from useless
|
// gain from useless
|
||||||
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
|
if (isNegative && !ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// source would leave the game
|
// source would leave the game
|
||||||
if (!card.hasSVar("EndOfTurnLeavePlay")) {
|
if (isNegative && !card.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +445,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (best.isEmpty()) {
|
if (best.isEmpty()) {
|
||||||
best = oppList;
|
best = filteredTgtList;
|
||||||
}
|
}
|
||||||
|
|
||||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||||
|
|||||||
@@ -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, CounterType.get(type));
|
addTargetsByCounterType(ai, sa, aiList, 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 = CounterType.get(CounterEnumType.M1M1);
|
final CounterType type = 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 = CounterType.get(CounterEnumType.POISON);
|
final CounterType poison = 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,9 +92,8 @@ 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)) {
|
||||||
@@ -124,7 +123,6 @@ 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();
|
||||||
@@ -170,7 +168,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(CounterType.get(CounterEnumType.M1M1)));
|
oppCreatM1 = CardLists.filter(oppCreatM1, input -> input.getNetToughness() <= 1 && input.canReceiveCounters(CounterEnumType.M1M1));
|
||||||
|
|
||||||
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
||||||
if (best != null) {
|
if (best != null) {
|
||||||
@@ -292,10 +290,8 @@ 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")) {
|
||||||
@@ -336,7 +332,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(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
if (!source.canReceiveCounters(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);
|
||||||
@@ -348,7 +344,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
return FightAi.canFightAi(ai, sa, nPump, nPump);
|
return FightAi.canFight(ai, sa, nPump, nPump);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amountStr.equals("X")) {
|
if (amountStr.equals("X")) {
|
||||||
@@ -442,17 +438,16 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
sa.addDividedAllocation(c, amount);
|
sa.addDividedAllocation(c, amount);
|
||||||
return decision;
|
return decision;
|
||||||
} else {
|
} else if (!hasSacCost) {
|
||||||
if (!hasSacCost) {
|
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||||
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
return decision;
|
||||||
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 {
|
||||||
@@ -608,7 +603,21 @@ 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.get(type));
|
final int currCounters = cards.get(0).getCounters(CounterType.getType(type));
|
||||||
|
|
||||||
|
// adding counters would cause counter amount to overflow
|
||||||
|
if (Integer.MAX_VALUE - currCounters <= amount) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
if (type.equals("P1P1")) {
|
||||||
|
if (Integer.MAX_VALUE - cards.get(0).getNetPower() <= amount) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
if (Integer.MAX_VALUE - cards.get(0).getNetToughness() <= amount) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// each non +1/+1 counter on the card is a 10% chance of not
|
// each non +1/+1 counter on the card is a 10% chance of not
|
||||||
// activating this ability.
|
// activating this ability.
|
||||||
|
|
||||||
@@ -623,7 +632,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.get(type), cards.get(0))) {
|
if (ComputerUtil.isUselessCounter(CounterType.getType(type), cards.get(0))) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,14 +679,12 @@ 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 = null;
|
CardCollection list;
|
||||||
|
|
||||||
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) {
|
||||||
@@ -693,9 +700,8 @@ 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()) {
|
||||||
@@ -737,9 +743,7 @@ 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.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParam("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);
|
||||||
@@ -758,14 +762,10 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("ChargeToBestCMC".equals(aiLogic)) {
|
if ("ChargeToBestCMC".equals(aiLogic)) {
|
||||||
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
if (mandatory) {
|
||||||
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,7 +789,6 @@ 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));
|
||||||
|
|
||||||
@@ -804,34 +803,32 @@ public class CountersPutAi extends CountersAi {
|
|||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String logic = sa.getParam("AILogic");
|
if ("Fight".equals(aiLogic) || "PowerDmg".equals(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.canFightAi(ai, sa, nPump, nPump);
|
AiAbilityDecision decision = FightAi.canFight(ai, sa, nPump, nPump);
|
||||||
if (decision.willingToPlay()) {
|
if (decision.willingToPlay()) {
|
||||||
return decision;
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
|
||||||
} else {
|
|
||||||
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
|
||||||
}
|
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
|
||||||
|
|
||||||
int totalTargets = list.size();
|
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
|
Iterable<Card> filteredField;
|
||||||
|
if (sa.isCurse()) {
|
||||||
|
filteredField = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
|
} else {
|
||||||
|
filteredField = ai.getCardsIn(ZoneType.Battlefield);
|
||||||
|
}
|
||||||
|
CardCollection list = CardLists.getTargetableCards(filteredField, sa);
|
||||||
|
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||||
|
int totalTargets = list.size();
|
||||||
|
boolean preferred = true;
|
||||||
|
|
||||||
while (sa.canAddMoreTarget()) {
|
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);
|
||||||
}
|
}
|
||||||
@@ -856,7 +853,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 = null;
|
Card choice;
|
||||||
|
|
||||||
// Choose targets here:
|
// Choose targets here:
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
@@ -865,33 +862,27 @@ 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 {
|
||||||
if (type.equals("M1M1")) {
|
choice = Aggregates.random(list);
|
||||||
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 {
|
||||||
if (preferred) {
|
choice = Aggregates.random(list);
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -961,8 +952,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 = CounterType.get(CounterEnumType.M1M1);
|
final CounterType m1m1 = CounterEnumType.M1M1;
|
||||||
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
final CounterType p1p1 = 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) {
|
||||||
@@ -981,9 +972,7 @@ 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);
|
||||||
|
|
||||||
final boolean isCurse = sa.isCurse();
|
if (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()) {
|
||||||
@@ -1080,11 +1069,10 @@ 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) {
|
if (e instanceof Card c) {
|
||||||
Card c = (Card) e;
|
|
||||||
if (c.getController().isOpponentOf(ai)) {
|
if (c.getController().isOpponentOf(ai)) {
|
||||||
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && !c.hasKeyword(Keyword.UNDYING)) {
|
if (options.contains(CounterEnumType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||||
return CounterType.get(CounterEnumType.M1M1);
|
return CounterEnumType.M1M1;
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (ComputerUtil.isNegativeCounter(type, c)) {
|
if (ComputerUtil.isNegativeCounter(type, c)) {
|
||||||
@@ -1098,15 +1086,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (e instanceof Player) {
|
} else if (e instanceof Player p) {
|
||||||
Player p = (Player) e;
|
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
if (options.contains(CounterType.get(CounterEnumType.POISON))) {
|
if (options.contains(CounterEnumType.POISON)) {
|
||||||
return CounterType.get(CounterEnumType.POISON);
|
return CounterEnumType.POISON;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.contains(CounterType.get(CounterEnumType.EXPERIENCE))) {
|
if (options.contains(CounterEnumType.EXPERIENCE)) {
|
||||||
return CounterType.get(CounterEnumType.EXPERIENCE);
|
return CounterEnumType.EXPERIENCE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1211,9 +1198,8 @@ 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) {
|
||||||
@@ -1233,9 +1219,8 @@ 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(CounterType.get(CounterEnumType.LOYALTY))) {
|
if (tgt.isPlaneswalker() && options.contains(CounterEnumType.LOYALTY)) {
|
||||||
return CounterType.get(CounterEnumType.LOYALTY);
|
return 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(CounterType.get(CounterEnumType.P1P1))) {
|
if (options.contains(CounterEnumType.P1P1)) {
|
||||||
return CounterType.get(CounterEnumType.P1P1);
|
return CounterEnumType.P1P1;
|
||||||
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
} else if (options.contains(CounterEnumType.M1M1)) {
|
||||||
return CounterType.get(CounterEnumType.M1M1);
|
return 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(CounterType.get(CounterEnumType.ICE))) {
|
if ("Dark Depths".equals(tgt.getName()) && options.contains(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 CounterType.get(CounterEnumType.ICE);
|
return CounterEnumType.ICE;
|
||||||
}
|
}
|
||||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterEnumType.P1P1)) {
|
||||||
return CounterType.get(CounterEnumType.P1P1);
|
return CounterEnumType.P1P1;
|
||||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterEnumType.M1M1)) {
|
||||||
return CounterType.get(CounterEnumType.M1M1);
|
return 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 CounterType.get(CounterEnumType.LOYALTY);
|
return 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(CounterType.get(CounterEnumType.M1M1)) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
if (options.contains(CounterEnumType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||||
return CounterType.get(CounterEnumType.M1M1);
|
return CounterEnumType.M1M1;
|
||||||
} else if (options.contains(CounterType.get(CounterEnumType.P1P1)) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
} else if (options.contains(CounterEnumType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||||
return CounterType.get(CounterEnumType.P1P1);
|
return 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().getNonactivePlayers().contains(ai)))))
|
&& !ai.getGame().getPhaseHandler().isPlayerTurn(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(). ??? )
|
||||||
|
|||||||
@@ -37,10 +37,6 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Never".equals(sa.getParam("AILogic"))) {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't deck yourself
|
// don't deck yourself
|
||||||
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
|
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
|
||||||
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ 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;
|
||||||
@@ -55,7 +54,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't tap creatures that may be able to block
|
// Don't tap creatures that may be able to block
|
||||||
@@ -74,9 +73,10 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// TODO: make this configurable in the AI profile
|
// TODO: make this configurable in the AI profile
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -174,9 +174,8 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) {
|
||||||
if (targetAI(ai, sa, sa.isTrigger() && sa.getHostCard().isInPlay())) {
|
if (targetAI(ai, sa, sa.isTrigger() && sa.getHostCard().isInPlay())) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -370,7 +369,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(CounterType.get(CounterEnumType.POISON))) {
|
if (getPoison != null && oppA.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
if (oppA.getPoisonCounters() + numCards > 9) {
|
if (oppA.getPoisonCounters() + numCards > 9) {
|
||||||
sa.getTargets().add(oppA);
|
sa.getTargets().add(oppA);
|
||||||
return true;
|
return true;
|
||||||
@@ -414,7 +413,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getPoison != null && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
if (getPoison != null && ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
if (numCards + ai.getPoisonCounters() >= 8) {
|
if (numCards + ai.getPoisonCounters() >= 8) {
|
||||||
aiTarget = false;
|
aiTarget = false;
|
||||||
}
|
}
|
||||||
@@ -472,7 +471,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ally would lose because of poison
|
// ally would lose because of poison
|
||||||
if (getPoison != null && ally.canReceiveCounters(CounterType.get(CounterEnumType.POISON)) && ally.getPoisonCounters() + numCards > 9) {
|
if (getPoison != null && ally.canReceiveCounters(CounterEnumType.POISON) && ally.getPoisonCounters() + numCards > 9) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,9 +540,8 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (targetAI(ai, sa, mandatory)) {
|
if (targetAI(ai, sa, mandatory)) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
|
||||||
}
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|||||||
@@ -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.canFightAi(ai, sa, 0,0);
|
return FightAi.canFight(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 canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
public static AiAbilityDecision canFight(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,15 +12,13 @@ import forge.game.spellability.SpellAbility;
|
|||||||
public class FlipACoinAi extends SpellAbilityAi {
|
public class FlipACoinAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#checkApiLogic(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(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("Never")) {
|
if (ailogic.equals("PhaseOut")) {
|
||||||
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 = CounterType.get(CounterEnumType.KI); // Petalmane Baku
|
CounterType ctrType = 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,12 +24,7 @@ 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) {
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
if (aiLogic.equals("LilianaMill")) {
|
||||||
|
|
||||||
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;
|
||||||
@@ -55,9 +50,10 @@ 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 true;
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|
||||||
|
|| 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,9 +38,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -63,11 +60,6 @@ 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);
|
||||||
@@ -81,13 +73,9 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty() && mandatory) {
|
||||||
if (sa.isTargetNumberValid()) {
|
list = CardUtil.getValidCardsToTarget(sa);
|
||||||
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,10 +33,8 @@ 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,7 +6,6 @@ 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;
|
||||||
@@ -65,7 +64,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(CounterType.get(CounterEnumType.POISON))) {
|
} else if (mandatory || !ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
// mandatory or ai is uneffected
|
// mandatory or ai is uneffected
|
||||||
result = true;
|
result = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -90,7 +89,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(CounterType.get(CounterEnumType.POISON))) {
|
} else if (!input.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -109,7 +108,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(CounterType.get(CounterEnumType.POISON))) {
|
if (ai.canBeTargetedBy(sa) && !ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -121,7 +120,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
|
return !input.canReceiveCounters(CounterEnumType.POISON);
|
||||||
});
|
});
|
||||||
if (!betterAllies.isEmpty()) {
|
if (!betterAllies.isEmpty()) {
|
||||||
allies = betterAllies;
|
allies = betterAllies;
|
||||||
|
|||||||
@@ -339,11 +339,11 @@ public class PumpAi extends PumpAiBase {
|
|||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pumpTgtAI(ai, sa, defense, attack, false, false)) {
|
if (pumpTgtAI(ai, sa, defense, attack, false, false)) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory,
|
private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory,
|
||||||
@@ -453,7 +453,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFight) {
|
if (isFight) {
|
||||||
return FightAi.canFightAi(ai, sa, attack, defense).willingToPlay();
|
return FightAi.canFight(ai, sa, attack, defense).willingToPlay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
boolean result = ai.getCreaturesInPlay().anyMatch(c -> c.isValid(valid, source.getController(), source, sa)
|
boolean result = ai.getCreaturesInPlay().anyMatch(c -> c.isValid(valid, source.getController(), source, sa)
|
||||||
&& ComputerUtilCard.shouldPumpCard(ai, sa, c, defense, power, keywords));
|
&& ComputerUtilCard.shouldPumpCard(ai, sa, c, defense, power, keywords));
|
||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} // pumpAllCanPlayAI()
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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;
|
||||||
@@ -40,7 +39,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(CounterType.get(CounterEnumType.TIME), target);
|
return !ComputerUtil.isNegativeCounter(CounterEnumType.TIME, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ 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,8 +29,6 @@ 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;
|
||||||
@@ -79,7 +77,6 @@ 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;
|
||||||
@@ -881,7 +878,7 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// stream().toList() causes crash on Android, use Collectors.toList()
|
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
||||||
List<String> NIF = new ArrayList<>(NIF_Q).stream().sorted().collect(Collectors.toList());
|
List<String> 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,8 +45,6 @@ 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);
|
||||||
@@ -303,7 +301,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()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
|
if (filteredCards.contains(rule.getName()))
|
||||||
continue;
|
continue;
|
||||||
for (ICardFace face : rule.getAllFaces()) {
|
for (ICardFace face : rule.getAllFaces()) {
|
||||||
addFaceToDbNames(face);
|
addFaceToDbNames(face);
|
||||||
@@ -501,8 +499,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addCard(PaperCard paperCard) {
|
public void addCard(PaperCard paperCard) {
|
||||||
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
|
if (filtered.contains(paperCard.getName())) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
allCardsByName.put(paperCard.getName(), paperCard);
|
allCardsByName.put(paperCard.getName(), paperCard);
|
||||||
|
|
||||||
@@ -523,17 +522,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean excludeCard(String cardName, String cardEdition) {
|
|
||||||
if (filtered.isEmpty())
|
|
||||||
return false;
|
|
||||||
if (filtered.contains(cardName)) {
|
|
||||||
if (exlcudedCardSet.equalsIgnoreCase(cardEdition) && exlcudedCardName.equalsIgnoreCase(cardName))
|
|
||||||
return true;
|
|
||||||
else return !exlcudedCardName.equalsIgnoreCase(cardName);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reIndex() {
|
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,6 +52,14 @@ 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,
|
||||||
@@ -275,18 +283,22 @@ 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 boolean foilAlwaysInCommonSlot = false;
|
private String additionalUnlockSet = "";
|
||||||
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
||||||
|
|
||||||
|
// Replace all of these things with booster slots
|
||||||
|
private boolean foilAlwaysInCommonSlot = false;
|
||||||
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;
|
||||||
@@ -373,7 +385,6 @@ 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; }
|
||||||
@@ -619,7 +630,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(
|
||||||
@@ -628,7 +639,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();
|
||||||
@@ -649,31 +660,37 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse sections of the format "<collector number> <rarity> <name>"
|
if (sectionName.endsWith("Types")) {
|
||||||
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
CardType.Helper.parseTypes(sectionName, contents.get(sectionName));
|
||||||
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>"
|
|
||||||
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
|
|
||||||
} else {
|
} else {
|
||||||
// save custom print sheets of the format "<amount> <name>|<setcode>|<art index>"
|
// Parse cards
|
||||||
// to parse later when printsheets are loaded lazily (and the cardpool is already initialized)
|
|
||||||
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
|
// parse sections of the format "<collector number> <rarity> <name>"
|
||||||
|
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
||||||
|
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>"
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -802,7 +819,6 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
res.additionalUnlockSet = metadata.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
|
res.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
|
||||||
@@ -810,6 +826,23 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,7 +873,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.getName(), item);
|
else map.put(item.getCode(), 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");
|
||||||
@@ -985,16 +1018,13 @@ 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;
|
||||||
}
|
}
|
||||||
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
return ed.hasBasicLands();
|
||||||
if (null == StaticData.instance().getCommonCards().getCard(landName, ed.getCode(), 0))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,7 +1045,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 (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
|
if (this.getCardInSet(landName).isEmpty())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ 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) {
|
||||||
@@ -167,21 +168,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTransformable() {
|
public boolean isTransformable() {
|
||||||
if (CardSplitType.Transform == getSplitType()) {
|
return CardSplitType.Transform == getSplitType() || CardSplitType.Modal == 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() {
|
||||||
@@ -220,7 +207,9 @@ 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() {
|
||||||
@@ -335,6 +324,15 @@ 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("Partner - Character select") && b.hasKeyword("Partner - Character select")) {
|
||||||
|
legal = true; // TMNT Commander deck 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()) {
|
||||||
@@ -353,6 +351,7 @@ 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("Partner - Character select") ||
|
||||||
hasKeyword("Doctor's companion") || isDoctor());
|
hasKeyword("Doctor's companion") || isDoctor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,6 +372,9 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,6 +827,8 @@ 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,6 +196,31 @@ 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,4 +1066,74 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,12 @@
|
|||||||
*/
|
*/
|
||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
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;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,28 +35,64 @@ import java.util.stream.Stream;
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
|
public enum ColorSet implements Iterable<Color>, Serializable {
|
||||||
private static final long serialVersionUID = 794691267379929080L;
|
|
||||||
|
|
||||||
private final byte myColor;
|
C(Color.COLORLESS),
|
||||||
|
W(Color.WHITE),
|
||||||
|
U(Color.BLUE),
|
||||||
|
WU(Color.WHITE, Color.BLUE),
|
||||||
|
B(Color.BLACK),
|
||||||
|
WB(Color.WHITE, Color.BLACK),
|
||||||
|
UB(Color.BLUE, Color.BLACK),
|
||||||
|
WUB(Color.WHITE, Color.BLUE, Color.BLACK),
|
||||||
|
R(Color.RED),
|
||||||
|
RW(Color.RED, Color.WHITE),
|
||||||
|
UR(Color.BLUE, Color.RED),
|
||||||
|
URW(Color.BLUE, Color.RED, Color.WHITE),
|
||||||
|
BR(Color.BLACK, Color.RED),
|
||||||
|
RWB(Color.RED, Color.WHITE, Color.BLACK),
|
||||||
|
UBR(Color.BLUE, Color.BLACK, Color.RED),
|
||||||
|
WUBR(Color.WHITE, Color.BLUE, Color.BLACK, Color.RED),
|
||||||
|
G(Color.GREEN),
|
||||||
|
GW(Color.GREEN, Color.WHITE),
|
||||||
|
GU(Color.GREEN, Color.BLUE),
|
||||||
|
GWU(Color.GREEN, Color.WHITE, Color.BLUE),
|
||||||
|
BG(Color.BLACK, Color.GREEN),
|
||||||
|
WBG(Color.WHITE, Color.BLACK, Color.GREEN),
|
||||||
|
BGU(Color.BLACK, Color.GREEN, Color.BLUE),
|
||||||
|
GWUB(Color.GREEN, Color.WHITE, Color.BLUE, Color.BLACK),
|
||||||
|
RG(Color.RED, Color.GREEN),
|
||||||
|
RGW(Color.RED, Color.GREEN, Color.WHITE),
|
||||||
|
GUR(Color.GREEN, Color.BLUE, Color.RED),
|
||||||
|
RGWU(Color.RED, Color.GREEN, Color.WHITE, Color.BLUE),
|
||||||
|
BRG(Color.BLACK, Color.RED, Color.GREEN),
|
||||||
|
BRGW(Color.BLACK, Color.RED, Color.GREEN, Color.WHITE),
|
||||||
|
UBRG(Color.BLUE, Color.BLACK, Color.RED, Color.GREEN),
|
||||||
|
WUBRG(Color.WHITE, Color.BLUE, Color.BLACK, Color.RED, Color.GREEN)
|
||||||
|
;
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 794691267379929080L;
|
||||||
|
// needs to be before other static
|
||||||
|
|
||||||
|
private final Collection<Color> orderedShards;
|
||||||
private final float orderWeight;
|
private final float orderWeight;
|
||||||
|
|
||||||
private static final ColorSet[] cache = new ColorSet[32];
|
private ColorSet(final Color... ordered) {
|
||||||
|
this.orderedShards = Arrays.asList(ordered);
|
||||||
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
|
this.orderWeight = this.calcOrderWeight();
|
||||||
private static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
|
||||||
|
|
||||||
private ColorSet(final byte mask) {
|
|
||||||
this.myColor = mask;
|
|
||||||
this.orderWeight = this.getOrderWeight();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
if (cache[mask32] == null) {
|
return values()[mask32];
|
||||||
cache[mask32] = new ColorSet((byte) mask32);
|
}
|
||||||
|
|
||||||
|
public static ColorSet fromEnums(final Color... colors) {
|
||||||
|
byte mask = 0;
|
||||||
|
for (Color e : colors) {
|
||||||
|
mask |= e.getColorMask();
|
||||||
}
|
}
|
||||||
return cache[mask32];
|
return fromMask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ColorSet fromNames(final String... colors) {
|
public static ColorSet fromNames(final String... colors) {
|
||||||
@@ -98,7 +131,10 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public boolean hasAnyColor(final int colormask) {
|
public boolean hasAnyColor(final int colormask) {
|
||||||
return (this.myColor & colormask) != 0;
|
return (this.ordinal() & colormask) != 0;
|
||||||
|
}
|
||||||
|
public boolean hasAnyColor(final Color c) {
|
||||||
|
return this.orderedShards.contains(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,12 +145,12 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public boolean hasAllColors(final int colormask) {
|
public boolean hasAllColors(final int colormask) {
|
||||||
return (this.myColor & colormask) == colormask;
|
return (this.ordinal() & colormask) == colormask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** this has exactly the colors defined by operand. */
|
/** this has exactly the colors defined by operand. */
|
||||||
public boolean hasExactlyColor(final int colormask) {
|
public boolean hasExactlyColor(final int colormask) {
|
||||||
return this.myColor == colormask;
|
return this.ordinal() == colormask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** this has no other colors except defined by operand. */
|
/** this has no other colors except defined by operand. */
|
||||||
@@ -124,17 +160,17 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
|
|
||||||
/** this has no other colors except defined by operand. */
|
/** this has no other colors except defined by operand. */
|
||||||
public boolean hasNoColorsExcept(final int colormask) {
|
public boolean hasNoColorsExcept(final int colormask) {
|
||||||
return (this.myColor & ~colormask) == 0;
|
return (this.ordinal() & ~colormask) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This returns the colors that colormask contains that are not in color */
|
/** This returns the colors that colormask contains that are not in color */
|
||||||
public ColorSet getMissingColors(final byte colormask) {
|
public ColorSet getMissingColors(final byte colormask) {
|
||||||
return fromMask(this.myColor & ~colormask);
|
return fromMask(this.ordinal() & ~colormask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Operand has no other colors except defined by this. */
|
/** Operand has no other colors except defined by this. */
|
||||||
public boolean containsAllColorsFrom(final int colorProfile) {
|
public boolean containsAllColorsFrom(final int colorProfile) {
|
||||||
return (~this.myColor & colorProfile) == 0;
|
return (~this.ordinal() & colorProfile) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,7 +179,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
* @return the int
|
* @return the int
|
||||||
*/
|
*/
|
||||||
public int countColors() {
|
public int countColors() {
|
||||||
return BinaryUtil.bitCount(this.myColor);
|
return BinaryUtil.bitCount(this.ordinal());
|
||||||
} // bit count
|
} // bit count
|
||||||
|
|
||||||
// order has to be: W U B R G multi colorless - same as cards numbering
|
// order has to be: W U B R G multi colorless - same as cards numbering
|
||||||
@@ -153,7 +189,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
*
|
*
|
||||||
* @return the order weight
|
* @return the order weight
|
||||||
*/
|
*/
|
||||||
private float getOrderWeight() {
|
private float calcOrderWeight() {
|
||||||
float res = this.countColors();
|
float res = this.countColors();
|
||||||
if (hasWhite()) {
|
if (hasWhite()) {
|
||||||
res += 0.0005f;
|
res += 0.0005f;
|
||||||
@@ -172,6 +208,10 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
public float getOrderWeight()
|
||||||
|
{
|
||||||
|
return orderWeight;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if is colorless.
|
* Checks if is colorless.
|
||||||
@@ -179,7 +219,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
* @return true, if is colorless
|
* @return true, if is colorless
|
||||||
*/
|
*/
|
||||||
public boolean isColorless() {
|
public boolean isColorless() {
|
||||||
return this.myColor == 0;
|
return this == C;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,7 +237,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
* @return true, if is all colors
|
* @return true, if is all colors
|
||||||
*/
|
*/
|
||||||
public boolean isAllColors() {
|
public boolean isAllColors() {
|
||||||
return this == ALL_COLORS;
|
return this == WUBRG;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -217,17 +257,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
* @return true, if is equal
|
* @return true, if is equal
|
||||||
*/
|
*/
|
||||||
public boolean isEqual(final byte color) {
|
public boolean isEqual(final byte color) {
|
||||||
return color == this.myColor;
|
return color == this.ordinal();
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int compareTo(final ColorSet other) {
|
|
||||||
return Float.compare(this.orderWeight, other.orderWeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Presets
|
// Presets
|
||||||
@@ -277,33 +307,13 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ColorSet inverse() {
|
public ColorSet inverse() {
|
||||||
byte mask = this.myColor;
|
byte mask = (byte)this.ordinal();
|
||||||
mask ^= MagicColor.ALL_COLORS;
|
mask ^= MagicColor.ALL_COLORS;
|
||||||
return fromMask(mask);
|
return fromMask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getColor() {
|
public byte getColor() {
|
||||||
return myColor;
|
return (byte)ordinal();
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see java.lang.Object#toString()
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
final ManaCostShard[] orderedShards = getOrderedShards();
|
|
||||||
return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the null color.
|
|
||||||
*
|
|
||||||
* @return the nullColor
|
|
||||||
*/
|
|
||||||
public static ColorSet getNullColor() {
|
|
||||||
return NO_COLORS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -313,7 +323,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public boolean sharesColorWith(final ColorSet ccOther) {
|
public boolean sharesColorWith(final ColorSet ccOther) {
|
||||||
return (this.myColor & ccOther.myColor) != 0;
|
return (this.ordinal() & ccOther.ordinal()) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColorSet getSharedColors(final ColorSet ccOther) {
|
public ColorSet getSharedColors(final ColorSet ccOther) {
|
||||||
@@ -321,123 +331,24 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ColorSet getOffColors(final ColorSet ccOther) {
|
public ColorSet getOffColors(final ColorSet ccOther) {
|
||||||
return fromMask(~this.myColor & ccOther.myColor);
|
return fromMask(~this.ordinal() & ccOther.ordinal());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Color> toEnumSet() {
|
public Set<Color> toEnumSet() {
|
||||||
if (isColorless()) {
|
return EnumSet.copyOf(orderedShards);
|
||||||
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
|
||||||
public Iterator<Byte> iterator() {
|
public Iterator<Color> iterator() {
|
||||||
return new ColorIterator();
|
return this.orderedShards.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ColorIterator extends UnmodifiableIterator<Byte> {
|
public Stream<Color> stream() {
|
||||||
int currentBit = -1;
|
return this.orderedShards.stream();
|
||||||
|
|
||||||
private int getIndexOfNextColor(){
|
|
||||||
int nextBit = currentBit + 1;
|
|
||||||
while (nextBit < MagicColor.NUMBER_OR_COLORS) {
|
|
||||||
if ((myColor & MagicColor.WUBRG[nextBit]) != 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
nextBit++;
|
|
||||||
}
|
|
||||||
return nextBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return getIndexOfNextColor() < MagicColor.NUMBER_OR_COLORS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Byte next() {
|
|
||||||
currentBit = getIndexOfNextColor();
|
|
||||||
if (currentBit >= MagicColor.NUMBER_OR_COLORS) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return MagicColor.WUBRG[currentBit];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream<MagicColor.Color> 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 ManaCostShard[] getOrderedShards() {
|
public Collection<Color> getOrderedColors() {
|
||||||
return shardOrderLookup[myColor];
|
return orderedShards;
|
||||||
}
|
|
||||||
|
|
||||||
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 };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
75
forge-core/src/main/java/forge/card/DraftOptions.java
Normal file
75
forge-core/src/main/java/forge/card/DraftOptions.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package forge.card;
|
||||||
|
|
||||||
|
public class DraftOptions {
|
||||||
|
public enum DoublePick {
|
||||||
|
NEVER,
|
||||||
|
FIRST_PICK, // only first pick each pack
|
||||||
|
WHEN_POD_SIZE_IS_4, // only when pod size is 4, so you can pick two cards each time
|
||||||
|
ALWAYS // each time you receive a pack, you can pick two cards
|
||||||
|
};
|
||||||
|
public enum DeckType {
|
||||||
|
Normal, // Standard deck, usually 40 cards
|
||||||
|
Commander // Special deck type for Commander format. Important for selection/construction
|
||||||
|
}
|
||||||
|
|
||||||
|
private DoublePick doublePick = DoublePick.NEVER;
|
||||||
|
private final int maxPodSize; // Usually 8, but could be smaller for cubes. I guess it could be larger too
|
||||||
|
private final int recommendedPodSize; // Usually 8, but is 4 for new double pick
|
||||||
|
private final int maxMatchPlayers; // Usually 2, but 4 for things like Commander or Conspiracy
|
||||||
|
private final DeckType deckType; // Normal or Commander
|
||||||
|
private final String freeCommander;
|
||||||
|
|
||||||
|
public DraftOptions(String doublePickOption, int maxPodSize, int recommendedPodSize, int maxMatchPlayers, String deckType, String freeCommander) {
|
||||||
|
this.maxPodSize = maxPodSize;
|
||||||
|
this.recommendedPodSize = recommendedPodSize;
|
||||||
|
this.maxMatchPlayers = maxMatchPlayers;
|
||||||
|
this.deckType = DeckType.valueOf(deckType);
|
||||||
|
this.freeCommander = freeCommander;
|
||||||
|
if (doublePickOption != null) {
|
||||||
|
switch (doublePickOption.toLowerCase()) {
|
||||||
|
case "firstpick":
|
||||||
|
doublePick = DoublePick.FIRST_PICK;
|
||||||
|
break;
|
||||||
|
case "always":
|
||||||
|
doublePick = DoublePick.ALWAYS;
|
||||||
|
break;
|
||||||
|
case "whenpodsizeis4":
|
||||||
|
doublePick = DoublePick.WHEN_POD_SIZE_IS_4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public int getMaxPodSize() {
|
||||||
|
return maxPodSize;
|
||||||
|
}
|
||||||
|
public int getRecommendedPodSize() {
|
||||||
|
return recommendedPodSize;
|
||||||
|
}
|
||||||
|
public DoublePick getDoublePick() {
|
||||||
|
return doublePick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DoublePick isDoublePick(int podSize) {
|
||||||
|
if (doublePick == DoublePick.WHEN_POD_SIZE_IS_4) {
|
||||||
|
if (podSize != 4) {
|
||||||
|
return DoublePick.NEVER;
|
||||||
|
}
|
||||||
|
// only when pod size is 4, so you can pick two cards each time
|
||||||
|
return DoublePick.ALWAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doublePick;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getMaxMatchPlayers() {
|
||||||
|
return maxMatchPlayers;
|
||||||
|
}
|
||||||
|
public DeckType getDeckType() {
|
||||||
|
return deckType;
|
||||||
|
}
|
||||||
|
public String getFreeCommander() {
|
||||||
|
return freeCommander;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,8 +75,6 @@ public interface ICardDatabase extends Iterable<PaperCard> {
|
|||||||
PaperCard getCardFromEditionsReleasedAfter(String cardName, CardArtPreference artPreference, int artIndex, Date releaseDate);
|
PaperCard getCardFromEditionsReleasedAfter(String cardName, CardArtPreference artPreference, int artIndex, Date releaseDate);
|
||||||
PaperCard getCardFromEditionsReleasedAfter(String cardName, CardArtPreference artPreference, int artIndex, Date releaseDate, Predicate<PaperCard> filter);
|
PaperCard getCardFromEditionsReleasedAfter(String cardName, CardArtPreference artPreference, int artIndex, Date releaseDate, Predicate<PaperCard> filter);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* CARDS COLLECTION RETRIEVAL METHODS
|
/* CARDS COLLECTION RETRIEVAL METHODS
|
||||||
* ================================== */
|
* ================================== */
|
||||||
Collection<PaperCard> getAllCards();
|
Collection<PaperCard> getAllCards();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
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.
|
||||||
@@ -157,21 +159,24 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Color {
|
public enum Color implements ITranslatable {
|
||||||
WHITE(Constant.WHITE, MagicColor.WHITE, "{W}"),
|
WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"),
|
||||||
BLUE(Constant.BLUE, MagicColor.BLUE, "{U}"),
|
BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"),
|
||||||
BLACK(Constant.BLACK, MagicColor.BLACK, "{B}"),
|
BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"),
|
||||||
RED(Constant.RED, MagicColor.RED, "{R}"),
|
RED(Constant.RED, MagicColor.RED, "R", "lblRed"),
|
||||||
GREEN(Constant.GREEN, MagicColor.GREEN, "{G}"),
|
GREEN(Constant.GREEN, MagicColor.GREEN, "G", "lblGreen"),
|
||||||
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "{C}");
|
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "C", "lblColorless");
|
||||||
|
|
||||||
private final String name, symbol;
|
private final String name, shortName, symbol;
|
||||||
|
private final String label;
|
||||||
private final byte colormask;
|
private final byte colormask;
|
||||||
|
|
||||||
Color(String name0, byte colormask0, String symbol0) {
|
Color(String name0, byte colormask0, String shortName, String label) {
|
||||||
name = name0;
|
name = name0;
|
||||||
colormask = colormask0;
|
colormask = colormask0;
|
||||||
symbol = symbol0;
|
this.shortName = shortName;
|
||||||
|
symbol = "{" + shortName + "}";
|
||||||
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color fromByte(final byte color) {
|
public static Color fromByte(final byte color) {
|
||||||
@@ -185,25 +190,25 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
public String getShortName() {
|
||||||
public String getLocalizedName() {
|
return shortName;
|
||||||
//Should probably move some of this logic back here, or at least to a more general location.
|
|
||||||
return DeckRecognizer.getLocalisedMagicColorName(getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getColormask() {
|
@Override
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package forge.card.mana;
|
package forge.card.mana;
|
||||||
|
|
||||||
|
import forge.card.ColorSet;
|
||||||
import forge.util.BinaryUtil;
|
import forge.util.BinaryUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,52 +35,51 @@ public enum ManaCostShard {
|
|||||||
COLORLESS(ManaAtom.COLORLESS, "C"),
|
COLORLESS(ManaAtom.COLORLESS, "C"),
|
||||||
|
|
||||||
/* Hybrid */
|
/* Hybrid */
|
||||||
WU(ManaAtom.WHITE | ManaAtom.BLUE, "W/U", "WU"),
|
WU(ManaAtom.WHITE | ManaAtom.BLUE, "W/U"),
|
||||||
WB(ManaAtom.WHITE | ManaAtom.BLACK, "W/B", "WB"),
|
WB(ManaAtom.WHITE | ManaAtom.BLACK, "W/B"),
|
||||||
UB(ManaAtom.BLUE | ManaAtom.BLACK, "U/B", "UB"),
|
UB(ManaAtom.BLUE | ManaAtom.BLACK, "U/B"),
|
||||||
UR(ManaAtom.BLUE | ManaAtom.RED, "U/R", "UR"),
|
UR(ManaAtom.BLUE | ManaAtom.RED, "U/R"),
|
||||||
BR(ManaAtom.BLACK | ManaAtom.RED, "B/R", "BR"),
|
BR(ManaAtom.BLACK | ManaAtom.RED, "B/R"),
|
||||||
BG(ManaAtom.BLACK | ManaAtom.GREEN, "B/G", "BG"),
|
BG(ManaAtom.BLACK | ManaAtom.GREEN, "B/G"),
|
||||||
RW(ManaAtom.RED | ManaAtom.WHITE, "R/W", "RW"),
|
RW(ManaAtom.RED | ManaAtom.WHITE, "R/W"),
|
||||||
RG(ManaAtom.RED | ManaAtom.GREEN, "R/G", "RG"),
|
RG(ManaAtom.RED | ManaAtom.GREEN, "R/G"),
|
||||||
GW(ManaAtom.GREEN | ManaAtom.WHITE, "G/W", "GW"),
|
GW(ManaAtom.GREEN | ManaAtom.WHITE, "G/W"),
|
||||||
GU(ManaAtom.GREEN | ManaAtom.BLUE, "G/U", "GU"),
|
GU(ManaAtom.GREEN | ManaAtom.BLUE, "G/U"),
|
||||||
|
|
||||||
/* Or 2 generic */
|
/* Or 2 generic */
|
||||||
W2(ManaAtom.WHITE | ManaAtom.OR_2_GENERIC, "2/W", "2W"),
|
W2(ManaAtom.WHITE | ManaAtom.OR_2_GENERIC, "2/W"),
|
||||||
U2(ManaAtom.BLUE | ManaAtom.OR_2_GENERIC, "2/U", "2U"),
|
U2(ManaAtom.BLUE | ManaAtom.OR_2_GENERIC, "2/U"),
|
||||||
B2(ManaAtom.BLACK | ManaAtom.OR_2_GENERIC, "2/B", "2B"),
|
B2(ManaAtom.BLACK | ManaAtom.OR_2_GENERIC, "2/B"),
|
||||||
R2(ManaAtom.RED | ManaAtom.OR_2_GENERIC, "2/R", "2R"),
|
R2(ManaAtom.RED | ManaAtom.OR_2_GENERIC, "2/R"),
|
||||||
G2(ManaAtom.GREEN | ManaAtom.OR_2_GENERIC, "2/G", "2G"),
|
G2(ManaAtom.GREEN | ManaAtom.OR_2_GENERIC, "2/G"),
|
||||||
|
|
||||||
/* Or Colorless */
|
/* Or Colorless */
|
||||||
CW(ManaAtom.WHITE | ManaAtom.COLORLESS, "C/W", "CW"),
|
CW(ManaAtom.WHITE | ManaAtom.COLORLESS, "C/W"),
|
||||||
CU(ManaAtom.BLUE | ManaAtom.COLORLESS, "C/U", "CU"),
|
CU(ManaAtom.BLUE | ManaAtom.COLORLESS, "C/U"),
|
||||||
CB(ManaAtom.BLACK | ManaAtom.COLORLESS, "C/B", "CB"),
|
CB(ManaAtom.BLACK | ManaAtom.COLORLESS, "C/B"),
|
||||||
CR(ManaAtom.RED | ManaAtom.COLORLESS, "C/R", "CR"),
|
CR(ManaAtom.RED | ManaAtom.COLORLESS, "C/R"),
|
||||||
CG(ManaAtom.GREEN | ManaAtom.COLORLESS, "C/G", "CG"),
|
CG(ManaAtom.GREEN | ManaAtom.COLORLESS, "C/G"),
|
||||||
|
|
||||||
// Snow and colorless
|
// Snow and colorless
|
||||||
S(ManaAtom.IS_SNOW, "S"),
|
S(ManaAtom.IS_SNOW, "S"),
|
||||||
GENERIC(ManaAtom.GENERIC, "1"),
|
GENERIC(ManaAtom.GENERIC, "1"),
|
||||||
|
|
||||||
|
|
||||||
/* Phyrexian */
|
/* Phyrexian */
|
||||||
WP(ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "W/P", "WP"),
|
WP(ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "W/P"),
|
||||||
UP(ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "U/P", "UP"),
|
UP(ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "U/P"),
|
||||||
BP(ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "B/P", "BP"),
|
BP(ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "B/P"),
|
||||||
RP(ManaAtom.RED | ManaAtom.OR_2_LIFE, "R/P", "RP"),
|
RP(ManaAtom.RED | ManaAtom.OR_2_LIFE, "R/P"),
|
||||||
GP(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "G/P", "GP"),
|
GP(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "G/P"),
|
||||||
BGP(ManaAtom.BLACK | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "B/G/P", "BGP"),
|
BGP(ManaAtom.BLACK | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "B/G/P"),
|
||||||
BRP(ManaAtom.BLACK | ManaAtom.RED | ManaAtom.OR_2_LIFE, "B/R/P", "BRP"),
|
BRP(ManaAtom.BLACK | ManaAtom.RED | ManaAtom.OR_2_LIFE, "B/R/P"),
|
||||||
GUP(ManaAtom.GREEN | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "G/U/P", "GUP"),
|
GUP(ManaAtom.GREEN | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "G/U/P"),
|
||||||
GWP(ManaAtom.GREEN | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "G/W/P", "GWP"),
|
GWP(ManaAtom.GREEN | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "G/W/P"),
|
||||||
RGP(ManaAtom.RED | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "R/G/P", "RGP"),
|
RGP(ManaAtom.RED | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "R/G/P"),
|
||||||
RWP(ManaAtom.RED | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "R/W/P", "RWP"),
|
RWP(ManaAtom.RED | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "R/W/P"),
|
||||||
UBP(ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "U/B/P", "UBP"),
|
UBP(ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "U/B/P"),
|
||||||
URP(ManaAtom.BLUE | ManaAtom.RED | ManaAtom.OR_2_LIFE, "U/R/P", "URP"),
|
URP(ManaAtom.BLUE | ManaAtom.RED | ManaAtom.OR_2_LIFE, "U/R/P"),
|
||||||
WBP(ManaAtom.WHITE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "W/B/P", "WBP"),
|
WBP(ManaAtom.WHITE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "W/B/P"),
|
||||||
WUP(ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "W/U/P", "WUP"),
|
WUP(ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "W/U/P"),
|
||||||
|
|
||||||
X(ManaAtom.IS_X, "X"),
|
X(ManaAtom.IS_X, "X"),
|
||||||
|
|
||||||
@@ -108,26 +108,12 @@ public enum ManaCostShard {
|
|||||||
* the s value
|
* the s value
|
||||||
*/
|
*/
|
||||||
ManaCostShard(final int value, final String sValue) {
|
ManaCostShard(final int value, final String sValue) {
|
||||||
this(value, sValue, sValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new card mana cost shard.
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
* the value
|
|
||||||
* @param sValue
|
|
||||||
* the s value
|
|
||||||
* @param imgKey
|
|
||||||
* the img key
|
|
||||||
*/
|
|
||||||
ManaCostShard(final int value, final String sValue, final String imgKey) {
|
|
||||||
this.shard = value;
|
this.shard = value;
|
||||||
this.cmc = this.getCMC();
|
this.cmc = this.getCMC();
|
||||||
this.cmpc = this.getCmpCost();
|
this.cmpc = this.getCmpCost();
|
||||||
this.stringValue = "{" + sValue + "}";
|
this.stringValue = "{" + sValue + "}";
|
||||||
this.shortStringValue = sValue;
|
this.shortStringValue = sValue;
|
||||||
this.imageKey = imgKey;
|
this.imageKey = sValue.replace("/", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int COLORS_SUPERPOSITION = ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.RED | ManaAtom.GREEN;
|
public static final int COLORS_SUPERPOSITION = ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.RED | ManaAtom.GREEN;
|
||||||
@@ -186,6 +172,10 @@ public enum ManaCostShard {
|
|||||||
return (byte)(this.shard & COLORS_SUPERPOSITION);
|
return (byte)(this.shard & COLORS_SUPERPOSITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final ColorSet getColor() {
|
||||||
|
return ColorSet.fromMask(getColorMask());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value of.
|
* Value of.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -115,6 +115,20 @@ 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);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import forge.StaticData;
|
|||||||
import forge.card.CardDb;
|
import forge.card.CardDb;
|
||||||
import forge.card.CardEdition;
|
import forge.card.CardEdition;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.item.IPaperCard;
|
import forge.item.IPaperCard;
|
||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
@@ -49,6 +50,16 @@ 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,
|
||||||
@@ -63,10 +74,14 @@ 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,
|
||||||
}
|
}
|
||||||
@@ -108,6 +123,10 @@ 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) {
|
||||||
@@ -126,6 +145,10 @@ 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
|
||||||
* ================================= */
|
* ================================= */
|
||||||
@@ -239,14 +262,11 @@ 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.
|
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY.
|
||||||
* False otherwise.
|
* False otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isCardToken() {
|
public boolean isCardToken() {
|
||||||
return (this.type == TokenType.LEGAL_CARD ||
|
return TokenType.CARD_TOKEN_TYPES.contains(this.type);
|
||||||
this.type == TokenType.LIMITED_CARD ||
|
|
||||||
this.type == TokenType.CARD_FROM_NOT_ALLOWED_SET ||
|
|
||||||
this.type == TokenType.CARD_FROM_INVALID_SET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -255,9 +275,7 @@ 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 (this.type == TokenType.LEGAL_CARD ||
|
return TokenType.IN_DECK_TOKEN_TYPES.contains(this.type);
|
||||||
this.type == TokenType.LIMITED_CARD ||
|
|
||||||
this.type == TokenType.DECK_NAME);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -266,7 +284,7 @@ public class DeckRecognizer {
|
|||||||
* False otherwise.
|
* False otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isCardTokenForDeck() {
|
public boolean isCardTokenForDeck() {
|
||||||
return (this.type == TokenType.LEGAL_CARD || this.type == TokenType.LIMITED_CARD);
|
return isCardToken() && isTokenForDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,10 +294,7 @@ 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 (this.type == TokenType.CARD_RARITY ||
|
return TokenType.CARD_PLACEHOLDER_TOKEN_TYPES.contains(this.type);
|
||||||
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
|
||||||
@@ -536,7 +551,7 @@ public class DeckRecognizer {
|
|||||||
PaperCard tokenCard = token.getCard();
|
PaperCard tokenCard = token.getCard();
|
||||||
|
|
||||||
if (isAllowed(tokenSection)) {
|
if (isAllowed(tokenSection)) {
|
||||||
if (!tokenSection.equals(referenceDeckSectionInParsing)) {
|
if (tokenSection != 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
|
||||||
@@ -575,7 +590,7 @@ public class DeckRecognizer {
|
|||||||
refLine = purgeAllLinks(refLine);
|
refLine = purgeAllLinks(refLine);
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
if (StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
if (refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
||||||
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
|
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
|
||||||
@@ -584,7 +599,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 (StringUtils.startsWith(line, ASTERISK)) // markdown lists (tappedout md export)
|
if (line.startsWith(ASTERISK)) // Markdown lists (tappedout md export)
|
||||||
line = line.substring(2);
|
line = line.substring(2);
|
||||||
|
|
||||||
// == Patches to Corner Cases
|
// == Patches to Corner Cases
|
||||||
@@ -600,8 +615,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 : StringUtils.startsWith(refLine, DOUBLE_SLASH) ||
|
return result != null ? result : refLine.startsWith(DOUBLE_SLASH) ||
|
||||||
StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
refLine.startsWith(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,7 +628,7 @@ public class DeckRecognizer {
|
|||||||
while (m.find()) {
|
while (m.find()) {
|
||||||
line = line.replaceAll(m.group(), "").trim();
|
line = line.replaceAll(m.group(), "").trim();
|
||||||
}
|
}
|
||||||
if (StringUtils.endsWith(line, "()"))
|
if (line.endsWith("()"))
|
||||||
return line.substring(0, line.length()-2);
|
return line.substring(0, line.length()-2);
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -741,21 +756,12 @@ 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;
|
DeckSection cardSection = switch (deckSec.toUpperCase().trim()) {
|
||||||
switch (deckSec.toUpperCase().trim()) {
|
case "MB" -> DeckSection.Main;
|
||||||
case "MB":
|
case "SB" -> DeckSection.Sideboard;
|
||||||
cardSection = DeckSection.Main;
|
case "CM" -> DeckSection.Commander;
|
||||||
break;
|
default -> DeckSection.matchingSection(card);
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -988,80 +994,37 @@ 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}", Localizer.getInstance().getMessage("lblMulticolor"));
|
||||||
return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol());
|
return String.format("%s %s", magicColor.getTranslatedName(), magicColor.getSymbol());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
private static String getMagicColourLabel(MagicColor.Color magicColor1, MagicColor.Color magicColor2) {
|
||||||
put(MagicColor.WHITE | MagicColor.BLUE, "WU");
|
|
||||||
put(MagicColor.BLUE | MagicColor.BLACK, "UB");
|
|
||||||
put(MagicColor.BLACK | MagicColor.RED, "BR");
|
|
||||||
put(MagicColor.RED | MagicColor.GREEN, "RG");
|
|
||||||
put(MagicColor.GREEN | MagicColor.WHITE, "GW");
|
|
||||||
put(MagicColor.WHITE | MagicColor.BLACK, "WB");
|
|
||||||
put(MagicColor.BLUE | MagicColor.RED, "UR");
|
|
||||||
put(MagicColor.BLACK | MagicColor.GREEN, "BG");
|
|
||||||
put(MagicColor.RED | MagicColor.WHITE, "RW");
|
|
||||||
put(MagicColor.GREEN | MagicColor.BLUE, "GU");
|
|
||||||
}};
|
|
||||||
private static String getMagicColourLabel(MagicColor.Color magicColor1, MagicColor.Color magicColor2){
|
|
||||||
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.getLocalizedName();
|
String localisedName1 = magicColor1.getTranslatedName();
|
||||||
String localisedName2 = magicColor2.getLocalizedName();
|
String localisedName2 = magicColor2.getTranslatedName();
|
||||||
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
|
return String.format("%s/%s {%s}", localisedName1, localisedName2, ColorSet.fromEnums(magicColor1, magicColor2));
|
||||||
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();
|
||||||
switch(colorName.toLowerCase()){
|
return switch (colorName.toLowerCase()) {
|
||||||
case MagicColor.Constant.WHITE:
|
case MagicColor.Constant.WHITE -> localizer.getMessage("lblWhite");
|
||||||
return localizer.getMessage("lblWhite");
|
case MagicColor.Constant.BLUE -> localizer.getMessage("lblBlue");
|
||||||
|
case MagicColor.Constant.BLACK -> localizer.getMessage("lblBlack");
|
||||||
case MagicColor.Constant.BLUE:
|
case MagicColor.Constant.RED -> localizer.getMessage("lblRed");
|
||||||
return localizer.getMessage("lblBlue");
|
case MagicColor.Constant.GREEN -> localizer.getMessage("lblGreen");
|
||||||
|
case MagicColor.Constant.COLORLESS -> localizer.getMessage("lblColorless");
|
||||||
case MagicColor.Constant.BLACK:
|
case "multicolour", "multicolor" -> localizer.getMessage("lblMulticolor");
|
||||||
return localizer.getMessage("lblBlack");
|
default -> "";
|
||||||
|
};
|
||||||
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 "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1080,37 +1043,6 @@ 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,9 +52,4 @@ 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -375,7 +375,8 @@ 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) {
|
||||||
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
pc = StaticData.instance().getCommonCards().createUnsupportedCard(name);
|
||||||
|
//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());
|
||||||
}
|
}
|
||||||
@@ -592,7 +593,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.getNullColor();
|
markedColors = ColorSet.C;
|
||||||
return new PaperCardFlags(this, markedColors, null);
|
return new PaperCardFlags(this, markedColors, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,42 @@ public abstract class PaperCardPredicates {
|
|||||||
return new PredicateFoil(isFoil);
|
return new PredicateFoil(isFoil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters cards that were printed in any of the specified editions.
|
||||||
|
*/
|
||||||
|
public static Predicate<PaperCard> printedInAnyEditions(final String[] editionCodes) {
|
||||||
|
Set<String> editions = new HashSet<>(Arrays.asList(editionCodes));
|
||||||
|
|
||||||
|
return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream()
|
||||||
|
.map(PaperCard::getEdition).anyMatch(editionCode ->
|
||||||
|
editions.contains(editionCode) &&
|
||||||
|
StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters cards that only printed in any of the specified editions.
|
||||||
|
*/
|
||||||
|
public static Predicate<PaperCard> onlyPrintedInEditions(final String[] editionCodes) {
|
||||||
|
Set<String> editions = new HashSet<>(Arrays.asList(editionCodes));
|
||||||
|
|
||||||
|
return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream()
|
||||||
|
.map(PaperCard::getEdition).allMatch(editionCode ->
|
||||||
|
editions.contains(editionCode) &&
|
||||||
|
StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters cards that are obtainable in any edition.
|
||||||
|
*/
|
||||||
|
public static Predicate<PaperCard> isObtainableAnyEdition() {
|
||||||
|
return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream()
|
||||||
|
.map(PaperCard::getEdition).anyMatch(editionCode ->
|
||||||
|
StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static final class PredicatePrintedWithRarity implements Predicate<PaperCard> {
|
private static final class PredicatePrintedWithRarity implements Predicate<PaperCard> {
|
||||||
private final CardRarity matchingRarity;
|
private final CardRarity matchingRarity;
|
||||||
|
|
||||||
@@ -76,25 +112,19 @@ public abstract class PaperCardPredicates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class PredicateColor implements Predicate<PaperCard> {
|
private static final class PredicateColor implements Predicate<PaperCard> {
|
||||||
private final byte operand;
|
private final MagicColor.Color operand;
|
||||||
|
|
||||||
private PredicateColor(final byte color) {
|
private PredicateColor(final MagicColor.Color color) {
|
||||||
this.operand = color;
|
this.operand = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(final PaperCard card) {
|
public boolean test(final PaperCard card) {
|
||||||
for (final byte color : card.getRules().getColor()) {
|
if (card.getRules().getColor().hasAnyColor(operand)) {
|
||||||
if (color == operand) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (card.getRules().getType().hasType(CardType.CoreType.Land)) {
|
if (card.getRules().getType().hasType(CardType.CoreType.Land) && card.getRules().getColorIdentity().hasAnyColor(operand)) {
|
||||||
for (final byte color : card.getRules().getColorIdentity()) {
|
return true;
|
||||||
if (color == operand) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -199,11 +229,11 @@ public abstract class PaperCardPredicates {
|
|||||||
public static final Predicate<PaperCard> IS_RARE_OR_MYTHIC = PaperCardPredicates.IS_RARE.or(PaperCardPredicates.IS_MYTHIC_RARE);
|
public static final Predicate<PaperCard> IS_RARE_OR_MYTHIC = PaperCardPredicates.IS_RARE.or(PaperCardPredicates.IS_MYTHIC_RARE);
|
||||||
public static final Predicate<PaperCard> IS_SPECIAL = new PredicateRarity(CardRarity.Special);
|
public static final Predicate<PaperCard> IS_SPECIAL = new PredicateRarity(CardRarity.Special);
|
||||||
public static final Predicate<PaperCard> IS_BASIC_LAND_RARITY = new PredicateRarity(CardRarity.BasicLand);
|
public static final Predicate<PaperCard> IS_BASIC_LAND_RARITY = new PredicateRarity(CardRarity.BasicLand);
|
||||||
public static final Predicate<PaperCard> IS_BLACK = new PredicateColor(MagicColor.BLACK);
|
public static final Predicate<PaperCard> IS_BLACK = new PredicateColor(MagicColor.Color.BLACK);
|
||||||
public static final Predicate<PaperCard> IS_BLUE = new PredicateColor(MagicColor.BLUE);
|
public static final Predicate<PaperCard> IS_BLUE = new PredicateColor(MagicColor.Color.BLUE);
|
||||||
public static final Predicate<PaperCard> IS_GREEN = new PredicateColor(MagicColor.GREEN);
|
public static final Predicate<PaperCard> IS_GREEN = new PredicateColor(MagicColor.Color.GREEN);
|
||||||
public static final Predicate<PaperCard> IS_RED = new PredicateColor(MagicColor.RED);
|
public static final Predicate<PaperCard> IS_RED = new PredicateColor(MagicColor.Color.RED);
|
||||||
public static final Predicate<PaperCard> IS_WHITE = new PredicateColor(MagicColor.WHITE);
|
public static final Predicate<PaperCard> IS_WHITE = new PredicateColor(MagicColor.Color.WHITE);
|
||||||
public static final Predicate<PaperCard> IS_COLORLESS = paperCard -> paperCard.getRules().getColor().isColorless();
|
public static final Predicate<PaperCard> IS_COLORLESS = paperCard -> paperCard.getRules().getColor().isColorless();
|
||||||
public static final Predicate<PaperCard> IS_UNREBALANCED = PaperCard::isUnRebalanced;
|
public static final Predicate<PaperCard> IS_UNREBALANCED = PaperCard::isUnRebalanced;
|
||||||
public static final Predicate<PaperCard> IS_REBALANCED = PaperCard::isRebalanced;
|
public static final Predicate<PaperCard> IS_REBALANCED = PaperCard::isRebalanced;
|
||||||
|
|||||||
@@ -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;
|
return cst == CardSplitType.Transform || cst == CardSplitType.Modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -633,7 +633,10 @@ 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);
|
||||||
src = tryGetStaticSheet(sheetName).toFlatList();
|
PrintSheet fromSheet = tryGetStaticSheet(sheetName);
|
||||||
|
if (fromSheet == null)
|
||||||
|
throw new RuntimeException("PrintSheet Error: " + ps.getName() + " didn't find " + sheetName + " from " + mainCode);
|
||||||
|
src = fromSheet.toFlatList();
|
||||||
setPred = x -> true;
|
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,13 +10,11 @@ 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,8 +207,6 @@ 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";
|
||||||
@@ -252,6 +250,11 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -261,6 +264,10 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -281,8 +288,7 @@ 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.19.1</version>
|
<version>8.21.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jgrapht</groupId>
|
<groupId>org.jgrapht</groupId>
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ 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").build();
|
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription")
|
||||||
|
.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.getNullColor();
|
colors = ColorSet.C;
|
||||||
} else {
|
} else {
|
||||||
colors = cardState.getCard().getColor(cardState);
|
colors = cardState.getCard().getColor(cardState);
|
||||||
}
|
}
|
||||||
@@ -166,8 +166,6 @@ 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")) {
|
||||||
@@ -195,7 +193,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();
|
||||||
@@ -237,6 +235,8 @@ 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,6 +410,8 @@ 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,19 +414,6 @@ 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>
|
||||||
@@ -858,6 +845,8 @@ 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)) {
|
||||||
@@ -893,8 +882,6 @@ 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,6 +57,8 @@ 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;
|
||||||
@@ -220,10 +222,6 @@ 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);
|
||||||
@@ -753,26 +751,29 @@ 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
|
||||||
switch(name) {
|
try {
|
||||||
case Hand: return moveToHand(c, cause, params);
|
return switch (name) {
|
||||||
case Library: return moveToLibrary(c, libPosition, cause, params);
|
case Hand -> moveToHand(c, cause, params);
|
||||||
case Battlefield: return moveToPlay(c, c.getController(), cause, params);
|
case Library -> moveToLibrary(c, libPosition, cause, params);
|
||||||
case Graveyard: return moveToGraveyard(c, cause, params);
|
case Battlefield -> moveToPlay(c, c.getController(), cause, params);
|
||||||
case Exile:
|
case Graveyard -> moveToGraveyard(c, cause, params);
|
||||||
if (!c.canExiledBy(cause, true)) {
|
case Exile -> !c.canExiledBy(cause, true) ? null : exile(c, cause, params);
|
||||||
return null;
|
case Stack -> moveToStack(c, cause, params);
|
||||||
}
|
case PlanarDeck, SchemeDeck, AttractionDeck, ContraptionDeck -> moveToVariantDeck(c, name, libPosition, cause, params);
|
||||||
return exile(c, cause, params);
|
case Junkyard -> moveToJunkyard(c, cause, params);
|
||||||
case Stack: return moveToStack(c, cause, params);
|
default -> moveTo(c.getOwner().getZone(name), c, cause); // sideboard will also get there
|
||||||
case PlanarDeck:
|
};
|
||||||
case SchemeDeck:
|
} catch (Exception e) {
|
||||||
case AttractionDeck:
|
String msg = "GameAction:moveTo: Exception occured";
|
||||||
case ContraptionDeck:
|
|
||||||
return moveToVariantDeck(c, name, libPosition, cause, params);
|
Breadcrumb bread = new Breadcrumb(msg);
|
||||||
case Junkyard:
|
bread.setData("Card", c.getName());
|
||||||
return moveToJunkyard(c, cause, params);
|
bread.setData("SA", cause.toString());
|
||||||
default: // sideboard will also get there
|
bread.setData("ZoneType", name.name());
|
||||||
return moveTo(c.getOwner().getZone(name), c, cause);
|
bread.setData("Player", c.getOwner());
|
||||||
|
Sentry.addBreadcrumb(bread);
|
||||||
|
|
||||||
|
throw new RuntimeException("Error in GameAction moveTo " + c.getName() + " to Player Zone " + name.name(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -974,6 +975,7 @@ 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();
|
||||||
}
|
}
|
||||||
@@ -1600,9 +1602,7 @@ 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.
|
||||||
if (!game.isGameOver()) {
|
checkGameOverCondition();
|
||||||
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 = CounterType.get(CounterEnumType.P1P1);
|
final CounterType p1p1 = CounterEnumType.P1P1;
|
||||||
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
final CounterType m1m1 = 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 = CounterType.get(CounterEnumType.DREAM);
|
final CounterType dreamType = CounterEnumType.DREAM;
|
||||||
|
|
||||||
int old = c.getCounters(dreamType);
|
int old = c.getCounters(dreamType);
|
||||||
if (old <= 0) {
|
if (old <= 0) {
|
||||||
@@ -1883,6 +1883,10 @@ 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;
|
||||||
@@ -2222,6 +2226,13 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
|
||||||
|
// Notify players
|
||||||
|
for (Player p : game.getPlayers()) {
|
||||||
|
p.getController().revealUnsupported(unsupported);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Delivers a message to all players. (use reveal to show Cards) */
|
/** 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,10 +125,22 @@ 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() && !sa.isLandAbility()) {
|
if (sa.isBasicSpell()) {
|
||||||
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;
|
||||||
@@ -166,18 +178,6 @@ 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,6 +242,7 @@ 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);
|
||||||
@@ -992,9 +993,6 @@ 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,17 +33,18 @@ 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;
|
||||||
@@ -197,14 +198,12 @@ 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,63 +221,83 @@ 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) {
|
||||||
// master mode
|
return cantBeAttachedMsg(attach, sa, checkSBA) == null;
|
||||||
if (!attach.isAttachment() || (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE))
|
}
|
||||||
|| equals(attach)) {
|
|
||||||
return false;
|
public String cantBeAttachedMsg(final Card attach, SpellAbility sa) {
|
||||||
|
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 false;
|
return attach.getName() + " is phased out";
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for rules
|
if (attach.isAura()) {
|
||||||
if (attach.isAura() && !canBeEnchantedBy(attach)) {
|
String msg = cantBeEnchantedByMsg(attach);
|
||||||
return false;
|
if (msg != null) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (attach.isEquipment() && !canBeEquippedBy(attach, sa)) {
|
if (attach.isEquipment()) {
|
||||||
return false;
|
String msg = cantBeEquippedByMsg(attach, sa);
|
||||||
|
if (msg != null) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (attach.isFortification() && !canBeFortifiedBy(attach)) {
|
if (attach.isFortification()) {
|
||||||
return false;
|
String msg = cantBeFortifiedByMsg(attach);
|
||||||
|
if (msg != null) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for can't attach static
|
StaticAbility stAb = StaticAbilityCantAttach.cantAttach(this, attach, checkSBA);
|
||||||
if (StaticAbilityCantAttach.cantAttach(this, attach, checkSBA)) {
|
if (stAb != null) {
|
||||||
return false;
|
return stAb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// true for all
|
return null;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean canBeEquippedBy(final Card aura, SpellAbility sa) {
|
protected String cantBeEquippedByMsg(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 false;
|
return getName() + " is not a Creature";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean canBeEnchantedBy(final Card aura) {
|
protected String cantBeFortifiedByMsg(final Card fort) {
|
||||||
|
/**
|
||||||
|
* 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 false;
|
return "No Enchant Keyword";
|
||||||
}
|
}
|
||||||
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||||
String k = ki.getOriginal();
|
if (ki instanceof KeywordWithType kwt) {
|
||||||
String m[] = k.split(":");
|
String v = kwt.getValidType();
|
||||||
String v = m[1];
|
String desc = kwt.getTypeDescription();
|
||||||
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||||
return false;
|
return getName() + " is not " + Lang.nounWithAmount(1, desc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCounters() {
|
public boolean hasCounters() {
|
||||||
@@ -305,9 +324,6 @@ 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) {
|
||||||
@@ -316,9 +332,6 @@ 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);
|
||||||
|
|
||||||
@@ -328,10 +341,6 @@ 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
|
||||||
@@ -351,18 +360,7 @@ 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,6 +15,7 @@ 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;
|
||||||
@@ -124,4 +125,12 @@ 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,6 +215,7 @@ 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,6 +23,7 @@ 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;
|
||||||
@@ -224,6 +225,7 @@ 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();
|
||||||
@@ -288,22 +290,32 @@ public class Match {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Deck myDeck = psc.getDeck();
|
Deck toCheck = psc.getDeck();
|
||||||
player.setDraftNotes(myDeck.getDraftNotes());
|
if (toCheck == null) {
|
||||||
|
try {
|
||||||
|
System.err.println(psc.getPlayer().getName() + " Deck is NULL...");
|
||||||
|
int val = rules.getGameType().getDeckFormat().getMainRange().getMinimum();
|
||||||
|
toCheck = new Deck("NULL");
|
||||||
|
if (val > 0)
|
||||||
|
toCheck.getMain().add("Wastes", val);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
Pair<Deck, List<PaperCard>> myDeck = toCheck.getValid();
|
||||||
|
player.setDraftNotes(myDeck.getLeft().getDraftNotes());
|
||||||
|
|
||||||
Set<PaperCard> myRemovedAnteCards = null;
|
Set<PaperCard> myRemovedAnteCards = null;
|
||||||
if (!rules.useAnte()) {
|
if (!rules.useAnte()) {
|
||||||
myRemovedAnteCards = getRemovedAnteCards(myDeck);
|
myRemovedAnteCards = getRemovedAnteCards(myDeck.getLeft());
|
||||||
for (PaperCard cp: myRemovedAnteCards) {
|
for (PaperCard cp: myRemovedAnteCards) {
|
||||||
for (Entry<DeckSection, CardPool> ds : myDeck) {
|
for (Entry<DeckSection, CardPool> ds : myDeck.getLeft()) {
|
||||||
ds.getValue().removeAll(cp);
|
ds.getValue().removeAll(cp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preparePlayerZone(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
|
preparePlayerZone(player, ZoneType.Library, myDeck.getLeft().getMain(), psc.useRandomFoil());
|
||||||
if (myDeck.has(DeckSection.Sideboard)) {
|
if (myDeck.getLeft().has(DeckSection.Sideboard)) {
|
||||||
preparePlayerZone(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
|
preparePlayerZone(player, ZoneType.Sideboard, myDeck.getLeft().get(DeckSection.Sideboard), psc.useRandomFoil());
|
||||||
|
|
||||||
// Assign Companion
|
// Assign Companion
|
||||||
Card companion = player.assignCompanion(game, person);
|
Card companion = player.assignCompanion(game, person);
|
||||||
@@ -322,7 +334,7 @@ public class Match {
|
|||||||
player.shuffle(null);
|
player.shuffle(null);
|
||||||
|
|
||||||
if (isFirstGame) {
|
if (isFirstGame) {
|
||||||
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
|
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck.getLeft());
|
||||||
if (cardsComplained != null && !cardsComplained.isEmpty()) {
|
if (cardsComplained != null && !cardsComplained.isEmpty()) {
|
||||||
rAICards.put(player, cardsComplained);
|
rAICards.put(player, cardsComplained);
|
||||||
}
|
}
|
||||||
@@ -337,6 +349,7 @@ 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();
|
||||||
@@ -347,6 +360,10 @@ 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,6 +239,10 @@ 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
|
||||||
|
|
||||||
@@ -359,9 +363,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -383,6 +384,9 @@ 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,7 +43,6 @@ 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");
|
||||||
|
|
||||||
@@ -523,6 +522,8 @@ 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")) {
|
||||||
@@ -1870,6 +1871,14 @@ 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")) {
|
||||||
@@ -2534,34 +2543,13 @@ public class AbilityUtils {
|
|||||||
return doXMath(CardLists.getValidCardCount(game.getLeftGraveyardThisTurn(), validFilter, player, c, ctb), expr, c, ctb);
|
return doXMath(CardLists.getValidCardCount(game.getLeftGraveyardThisTurn(), validFilter, player, c, ctb), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count$UnlockedDoors <Valid>
|
if (sq[0].equals("UnlockedDoors")) {
|
||||||
if (sq[0].startsWith("UnlockedDoors")) {
|
return doXMath(player.getUnlockedDoors().size(), expr, c, ctb);
|
||||||
final String[] workingCopy = l[0].split(" ", 2);
|
|
||||||
final String validFilter = workingCopy[1];
|
|
||||||
|
|
||||||
int unlocked = 0;
|
|
||||||
for (Card doorCard : CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validFilter, player, c, ctb)) {
|
|
||||||
unlocked += doorCard.getUnlockedRooms().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
return doXMath(unlocked, expr, c, ctb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count$DistinctUnlockedDoors <Valid>
|
|
||||||
// Counts the distinct names of unlocked doors. Used for the "Promising Stairs"
|
// Counts the distinct names of unlocked doors. Used for the "Promising Stairs"
|
||||||
if (sq[0].startsWith("DistinctUnlockedDoors")) {
|
if (sq[0].equals("DistinctUnlockedDoors")) {
|
||||||
final String[] workingCopy = l[0].split(" ", 2);
|
return doXMath(Sets.newHashSet(player.getUnlockedDoors()).size(), expr, c, ctb);
|
||||||
final String validFilter = workingCopy[1];
|
|
||||||
|
|
||||||
Set<String> viewedNames = new HashSet<>();
|
|
||||||
for (Card doorCard : CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validFilter, player, c, ctb)) {
|
|
||||||
for(CardStateName stateName : doorCard.getUnlockedRooms()) {
|
|
||||||
viewedNames.add(doorCard.getState(stateName).getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int distinctUnlocked = viewedNames.size();
|
|
||||||
|
|
||||||
return doXMath(distinctUnlocked, expr, c, ctb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manapool
|
// Manapool
|
||||||
@@ -2881,21 +2869,6 @@ 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);
|
||||||
@@ -2910,13 +2883,6 @@ 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()
|
||||||
@@ -3436,6 +3402,7 @@ 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);
|
||||||
|
|
||||||
@@ -3622,46 +3589,10 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -3740,10 +3671,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -3752,6 +3679,10 @@ 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.getKeyword() != null && cause.getKeyword().getStatic() != null) {
|
} else if (!cause.isSpell() && 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) {
|
||||||
@@ -46,7 +46,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 +55,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 +86,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,6 +46,9 @@ 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,7 +17,6 @@ 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;
|
||||||
@@ -86,7 +85,7 @@ public class AmassEffect extends TokenEffectBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> params = Maps.newHashMap();
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
|
params.put("CounterType", 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,6 +90,16 @@ 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) {
|
||||||
@@ -128,7 +138,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, 0, false);
|
c.addColor(colors, !overwrite, timestamp, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("LeaveBattlefield")) {
|
if (sa.hasParam("LeaveBattlefield")) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
|
|||||||
final String[] changedColorWordsArray = sa.getParam("ChangeColorWord").split(" ");
|
final String[] changedColorWordsArray = sa.getParam("ChangeColorWord").split(" ");
|
||||||
if (changedColorWordsArray[0].equals("Choose")) {
|
if (changedColorWordsArray[0].equals("Choose")) {
|
||||||
originalColor = sa.getActivatingPlayer().getController().chooseColor(
|
originalColor = sa.getActivatingPlayer().getController().chooseColor(
|
||||||
Localizer.getInstance().getMessage("lblChooseColorReplace"), sa, ColorSet.ALL_COLORS);
|
Localizer.getInstance().getMessage("lblChooseColorReplace"), sa, ColorSet.WUBRG);
|
||||||
changedColorWordOriginal = TextUtil.capitalize(MagicColor.toLongString(originalColor));
|
changedColorWordOriginal = TextUtil.capitalize(MagicColor.toLongString(originalColor));
|
||||||
} else {
|
} else {
|
||||||
changedColorWordOriginal = changedColorWordsArray[0];
|
changedColorWordOriginal = changedColorWordsArray[0];
|
||||||
@@ -44,7 +44,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
|
|||||||
if (changedColorWordsArray[1].equals("Choose")) {
|
if (changedColorWordsArray[1].equals("Choose")) {
|
||||||
final ColorSet possibleNewColors;
|
final ColorSet possibleNewColors;
|
||||||
if (originalColor == 0) { // no original color (ie. any or absent)
|
if (originalColor == 0) { // no original color (ie. any or absent)
|
||||||
possibleNewColors = ColorSet.ALL_COLORS;
|
possibleNewColors = ColorSet.WUBRG;
|
||||||
} else { // may choose any except original color
|
} else { // may choose any except original color
|
||||||
possibleNewColors = ColorSet.fromMask(originalColor).inverse();
|
possibleNewColors = ColorSet.fromMask(originalColor).inverse();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ 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 {
|
||||||
|
|
||||||
@@ -103,6 +104,7 @@ 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")) {
|
||||||
@@ -117,12 +119,11 @@ 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")) {
|
||||||
final String ct = sa.getParam("ChangeType");
|
List<String> typeList = Arrays.stream(sa.getParam("ChangeType").split(",")).map(ct -> CardType.isACardType(ct) ? ct.toLowerCase() : ct).collect(Collectors.toList());
|
||||||
type = CardType.CoreType.isValidEnum(ct) ? ct.toLowerCase() : ct;
|
type = Lang.joinHomogenous(typeList, null, num == 1 ? "or" : "and/or");
|
||||||
}
|
}
|
||||||
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()) {
|
||||||
@@ -152,6 +153,9 @@ 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) {
|
||||||
@@ -928,7 +932,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 = ZoneType.listValueOf(sa.getParam("Origin"));
|
origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
|
||||||
}
|
}
|
||||||
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
|
|
||||||
@@ -969,12 +973,10 @@ 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 {
|
||||||
if (defined) {
|
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
|
||||||
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)) {
|
||||||
@@ -1101,7 +1103,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
if (changeType.startsWith("EACH")) {
|
if (changeType.startsWith("EACH")) {
|
||||||
String[] eachTypes = changeType.substring(5).split(" & ");
|
String[] eachTypes = changeType.substring(5).split(" & ");
|
||||||
for (String thisType : eachTypes) {
|
for (String thisType : eachTypes) {
|
||||||
for (int i = 0; i < changeNum && destination != null; i++) {
|
for (int i = 0; i < changeNum; i++) {
|
||||||
CardCollection thisList = (CardCollection) AbilityUtils.filterListByType(fetchList, thisType, sa);
|
CardCollection thisList = (CardCollection) AbilityUtils.filterListByType(fetchList, thisType, sa);
|
||||||
if (!chosenCards.isEmpty()) {
|
if (!chosenCards.isEmpty()) {
|
||||||
thisList.removeAll(chosenCards);
|
thisList.removeAll(chosenCards);
|
||||||
@@ -1136,7 +1138,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
// maybe prompt the user if they selected fewer than the maximum possible?
|
// maybe prompt the user if they selected fewer than the maximum possible?
|
||||||
} else {
|
} else {
|
||||||
// one at a time
|
// one at a time
|
||||||
for (int i = 0; i < changeNum && destination != null; i++) {
|
for (int i = 0; i < changeNum; i++) {
|
||||||
if (sa.hasParam("DifferentNames")) {
|
if (sa.hasParam("DifferentNames")) {
|
||||||
for (Card c : chosenCards) {
|
for (Card c : chosenCards) {
|
||||||
fetchList = CardLists.filter(fetchList, CardPredicates.sharesNameWith(c).negate());
|
fetchList = CardLists.filter(fetchList, CardPredicates.sharesNameWith(c).negate());
|
||||||
@@ -1250,6 +1252,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
player.removeController(controlTimestamp);
|
player.removeController(controlTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("Exactly") && chosenCards.size() < changeNum) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
HiddenOriginChoices choices = new HiddenOriginChoices();
|
HiddenOriginChoices choices = new HiddenOriginChoices();
|
||||||
choices.searchedLibrary = searchedLibrary;
|
choices.searchedLibrary = searchedLibrary;
|
||||||
choices.shuffleMandatory = shuffleMandatory;
|
choices.shuffleMandatory = shuffleMandatory;
|
||||||
@@ -1279,7 +1285,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
Player decider = ObjectUtils.firstNonNull(chooser, player);
|
Player decider = ObjectUtils.firstNonNull(chooser, player);
|
||||||
|
|
||||||
for (final Card c : chosenCards) {
|
for (final Card c : chosenCards) {
|
||||||
Card movedCard = null;
|
Card movedCard;
|
||||||
final Zone originZone = game.getZoneOf(c);
|
final Zone originZone = game.getZoneOf(c);
|
||||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||||
moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary);
|
moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary);
|
||||||
@@ -1411,6 +1417,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
movedCard.addMayLookFaceDownExile(sa.getActivatingPlayer());
|
movedCard.addMayLookFaceDownExile(sa.getActivatingPlayer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (destination == null) {
|
||||||
|
movedCard = c;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
movedCard = game.getAction().moveTo(destination, c, 0, sa, moveParams);
|
movedCard = game.getAction().moveTo(destination, c, 0, sa, moveParams);
|
||||||
}
|
}
|
||||||
@@ -1476,7 +1485,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(sa.getOriginalHost(), sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
int cAmount = AbilityUtils.calculateAmount(source, 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,22 +287,17 @@ 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,24 +102,29 @@ 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"),
|
||||||
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
|
||||||
activator, source, sa);
|
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")) {
|
||||||
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 = choices.size();
|
max = srcCards.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 (choices.size() < min) {
|
if (srcCards.size() < min) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,13 +133,12 @@ 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(choices, sa, title, min, max, min == 0, params);
|
srcCards = pc.chooseCardsForEffect(srcCards, 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);
|
||||||
@@ -150,8 +154,6 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
srcCards = getTargetCards(sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Card tgtCard : srcCards) {
|
for (final Card tgtCard : srcCards) {
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ 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,6 +73,11 @@ 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")) {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class EarthbendEffect extends SpellAbilityEffect {
|
|||||||
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 +59,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());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Chosen Type
|
if (hostCard.getChosenDirection() != null) {
|
||||||
|
eff.setChosenDirection(hostCard.getChosenDirection());
|
||||||
|
}
|
||||||
|
|
||||||
if (hostCard.hasChosenType()) {
|
if (hostCard.hasChosenType()) {
|
||||||
eff.setChosenType(hostCard.getChosenType());
|
eff.setChosenType(hostCard.getChosenType());
|
||||||
}
|
}
|
||||||
@@ -292,12 +292,10 @@ 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,6 +34,9 @@ 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,33 +54,29 @@ 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 (sa.hasParam("RememberDifference")) {
|
if (life2 > life1) {
|
||||||
final int diff = life1 - life2;
|
// swap players
|
||||||
source.addRemembered(diff);
|
Player tmp = p2;
|
||||||
|
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 (!lossMap.isEmpty()) { // Run triggers if any player actually lost life
|
if (sa.hasParam("RememberDifference")) {
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
|
source.addRemembered(p1.getLife() - p2.getLife());
|
||||||
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class ManaEffect extends SpellAbilityEffect {
|
|||||||
for (int nChar = 0; nChar < colorsNeeded.length(); nChar++) {
|
for (int nChar = 0; nChar < colorsNeeded.length(); nChar++) {
|
||||||
mask |= MagicColor.fromName(colorsNeeded.charAt(nChar));
|
mask |= MagicColor.fromName(colorsNeeded.charAt(nChar));
|
||||||
}
|
}
|
||||||
colorMenu = mask == 0 ? ColorSet.ALL_COLORS : ColorSet.fromMask(mask);
|
colorMenu = mask == 0 ? ColorSet.WUBRG : ColorSet.fromMask(mask);
|
||||||
byte val = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
|
byte val = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
|
||||||
if (0 == val) {
|
if (0 == val) {
|
||||||
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName());
|
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName());
|
||||||
@@ -271,10 +271,11 @@ public class ManaEffect extends SpellAbilityEffect {
|
|||||||
producedMana.append(abMana.produceMana(mana, p, sa));
|
producedMana.append(abMana.produceMana(mana, p, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
|
|
||||||
|
|
||||||
// Only clear express choice after mana has been produced
|
// Only clear express choice after mana has been produced
|
||||||
abMana.clearExpressChoice();
|
abMana.clearExpressChoice();
|
||||||
|
|
||||||
|
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
|
||||||
|
|
||||||
if (sa.isKeyword(Keyword.FIREBENDING)) {
|
if (sa.isKeyword(Keyword.FIREBENDING)) {
|
||||||
activator.triggerElementalBend(TriggerType.Firebend);
|
activator.triggerElementalBend(TriggerType.Firebend);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public class MutateEffect extends SpellAbilityEffect {
|
|||||||
game.getTriggerHandler().clearActiveTriggers(target, null);
|
game.getTriggerHandler().clearActiveTriggers(target, null);
|
||||||
game.getTriggerHandler().registerActiveTrigger(target, false);
|
game.getTriggerHandler().registerActiveTrigger(target, false);
|
||||||
|
|
||||||
game.getAction().moveTo(p.getZone(ZoneType.Merged), host, sa, AbilityKey.newMap());
|
game.getAction().moveTo(p.getZone(ZoneType.Merged), host, sa);
|
||||||
|
|
||||||
host.setTapped(target.isTapped());
|
host.setTapped(target.isTapped());
|
||||||
host.setFlipped(target.isFlipped());
|
host.setFlipped(target.isFlipped());
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ 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(" ").append(source.getBasePowerString());
|
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ");
|
||||||
sb.append(" / ").append(source.getBaseToughnessString());
|
sb.append(sa.getParamOrDefault("SetPower", source.getBasePowerString()));
|
||||||
|
sb.append(" / ").append(sa.getParamOrDefault("SetToughness", source.getBaseToughnessString()));
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,6 +428,10 @@ 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")
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ public class ProtectEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
} else if (sa.getParam("Gains").startsWith("Defined")) {
|
} else if (sa.getParam("Gains").startsWith("Defined")) {
|
||||||
CardCollection def = AbilityUtils.getDefinedCards(host, sa.getParam("Gains").substring(8), sa);
|
CardCollection def = AbilityUtils.getDefinedCards(host, sa.getParam("Gains").substring(8), sa);
|
||||||
for (final Byte color : def.get(0).getColor()) {
|
for (final MagicColor.Color color : def.get(0).getColor()) {
|
||||||
gains.add(MagicColor.toLongString(color));
|
gains.add(color.getName());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gains.addAll(choices);
|
gains.addAll(choices);
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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;
|
||||||
@@ -282,6 +281,17 @@ 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(" & ")));
|
||||||
@@ -307,8 +317,6 @@ 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")) {
|
||||||
@@ -394,17 +402,6 @@ 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));
|
||||||
}
|
}
|
||||||
@@ -494,7 +491,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
|||||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), tgtCards);
|
registerDelayedTrigger(sa, sa.getParam("AtEOT"), tgtCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Card tgtC : untargetedCards) {
|
for (final Card tgtC : CardUtil.getRadiance(sa)) {
|
||||||
// only pump things in PumpZone
|
// only pump things in PumpZone
|
||||||
if (!tgtC.isInZones(pumpZones)) {
|
if (!tgtC.isInZones(pumpZones)) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
|
|||||||
// replace type and amount
|
// replace type and amount
|
||||||
replaced = sa.getParam("ReplaceMana");
|
replaced = sa.getParam("ReplaceMana");
|
||||||
if ("Any".equals(replaced)) {
|
if ("Any".equals(replaced)) {
|
||||||
byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS);
|
byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
|
||||||
replaced = MagicColor.toShortString(rs);
|
replaced = MagicColor.toShortString(rs);
|
||||||
}
|
}
|
||||||
} else if (sa.hasParam("ReplaceType")) {
|
} else if (sa.hasParam("ReplaceType")) {
|
||||||
// replace color and colorless
|
// replace color and colorless
|
||||||
String color = sa.getParam("ReplaceType");
|
String color = sa.getParam("ReplaceType");
|
||||||
if ("Any".equals(color)) {
|
if ("Any".equals(color)) {
|
||||||
byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS);
|
byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG);
|
||||||
color = MagicColor.toShortString(rs);
|
color = MagicColor.toShortString(rs);
|
||||||
} else {
|
} else {
|
||||||
// convert in case Color Word used
|
// convert in case Color Word used
|
||||||
|
|||||||
@@ -75,9 +75,8 @@ 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) {
|
||||||
filteredCards = CardLists.getValidCards(p.getCardsIn(leaveZone), leaveRestriction, p, sa.getHostCard(), sa);
|
List<Card> filteredCards = CardLists.getValidCards(p.getCardsIn(leaveZone), leaveRestriction, p, sa.getHostCard(), sa);
|
||||||
newLibrary.addAll(filteredCards);
|
newLibrary.addAll(filteredCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user