mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 09:48:02 +00:00
Compare commits
1 Commits
adv-draft-
...
reduce-bit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25e757cae9 |
4
.github/workflows/maven-publish.yml
vendored
4
.github/workflows/maven-publish.yml
vendored
@@ -129,9 +129,7 @@ jobs:
|
|||||||
makeLatest: true
|
makeLatest: true
|
||||||
|
|
||||||
- name: 🔧 Install XML tools
|
- name: 🔧 Install XML tools
|
||||||
run: |
|
run: sudo apt-get install -y libxml2-utils
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libxml2-utils
|
|
||||||
|
|
||||||
- name: 🔼 Bump versionCode in root POM
|
- name: 🔼 Bump versionCode in root POM
|
||||||
id: bump_version
|
id: bump_version
|
||||||
|
|||||||
2
.github/workflows/test-build.yaml
vendored
2
.github/workflows/test-build.yaml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: ['17', '21']
|
java: [ '17' ]
|
||||||
name: Test with Java ${{ matrix.Java }}
|
name: Test with Java ${{ matrix.Java }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -66,9 +66,6 @@ forge-gui-mobile-dev/testAssets
|
|||||||
|
|
||||||
forge-gui/res/cardsfolder/*.bat
|
forge-gui/res/cardsfolder/*.bat
|
||||||
|
|
||||||
# Generated changelog file
|
|
||||||
forge-gui/release-files/CHANGES.txt
|
|
||||||
|
|
||||||
forge-gui/res/PerSetTrackingResults
|
forge-gui/res/PerSetTrackingResults
|
||||||
forge-gui/res/decks
|
forge-gui/res/decks
|
||||||
forge-gui/res/layouts
|
forge-gui/res/layouts
|
||||||
@@ -90,7 +87,3 @@ forge-gui/tools/PerSetTrackingResults
|
|||||||
*.tiled-session
|
*.tiled-session
|
||||||
/forge-gui/res/adventure/*.tiled-project
|
/forge-gui/res/adventure/*.tiled-project
|
||||||
/forge-gui/res/adventure/*.tiled-session
|
/forge-gui/res/adventure/*.tiled-session
|
||||||
|
|
||||||
# Ignore python temporaries
|
|
||||||
__pycache__
|
|
||||||
*.pyc
|
|
||||||
|
|||||||
33
.gitlab/issue_templates/Bug.md
Normal file
33
.gitlab/issue_templates/Bug.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
Summary
|
||||||
|
|
||||||
|
(Summarize the bug encountered concisely)
|
||||||
|
|
||||||
|
|
||||||
|
Steps to reproduce
|
||||||
|
|
||||||
|
(How one can reproduce the issue - this is very important. Specific cards and specific actions especially)
|
||||||
|
|
||||||
|
|
||||||
|
Which version of Forge are you on (Release, Snapshot? Desktop, Android?)
|
||||||
|
|
||||||
|
|
||||||
|
What is the current bug behavior?
|
||||||
|
|
||||||
|
(What actually happens)
|
||||||
|
|
||||||
|
|
||||||
|
What is the expected correct behavior?
|
||||||
|
|
||||||
|
(What you should see instead)
|
||||||
|
|
||||||
|
|
||||||
|
Relevant logs and/or screenshots
|
||||||
|
|
||||||
|
(Paste/Attach your game.log from the crash - please use code blocks (```)) Also, provide screenshots of the current state.
|
||||||
|
|
||||||
|
|
||||||
|
Possible fixes
|
||||||
|
|
||||||
|
(If you can, link to the line of code that might be responsible for the problem)
|
||||||
|
|
||||||
|
/label ~needs-investigation
|
||||||
15
.gitlab/issue_templates/Feature.md
Normal file
15
.gitlab/issue_templates/Feature.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Summary
|
||||||
|
|
||||||
|
(Summarize the feature you wish concisely)
|
||||||
|
|
||||||
|
|
||||||
|
Example screenshots
|
||||||
|
|
||||||
|
(If this is a UI change, please provide an example screenshot of how this feature might work)
|
||||||
|
|
||||||
|
|
||||||
|
Feature type
|
||||||
|
|
||||||
|
(Where in Forge does this belong? e.g. Quest Mode, Deck Editor, Limited, Constructed, etc.)
|
||||||
|
|
||||||
|
/label ~feature request
|
||||||
@@ -15,7 +15,7 @@ public class Main {
|
|||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
GuiBase.setInterface(new GuiMobile(Files.exists(Paths.get("./res"))?"./":"../forge-gui/"));
|
GuiBase.setInterface(new GuiMobile(Files.exists(Paths.get("./res"))?"./":"../forge-gui/"));
|
||||||
GuiBase.setDeviceInfo(null, 0, 0, System.getProperty("user.home") + "/Downloads/");
|
GuiBase.setDeviceInfo("", "", 0, 0);
|
||||||
new EditorMainWindow(Config.instance());
|
new EditorMainWindow(Config.instance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ public class AiController {
|
|||||||
private int lastAttackAggression;
|
private int lastAttackAggression;
|
||||||
private boolean useLivingEnd;
|
private boolean useLivingEnd;
|
||||||
private List<SpellAbility> skipped;
|
private List<SpellAbility> skipped;
|
||||||
private boolean timeoutReached;
|
|
||||||
|
|
||||||
public AiController(final Player computerPlayer, final Game game0) {
|
public AiController(final Player computerPlayer, final Game game0) {
|
||||||
player = computerPlayer;
|
player = computerPlayer;
|
||||||
@@ -887,8 +886,27 @@ public class AiController {
|
|||||||
private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) {
|
private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
|
|
||||||
if (sa.hasParam("AICheckSVar") && !aiShouldRun(sa, sa, host, null)) {
|
// Check a predefined condition
|
||||||
return AiPlayDecision.AnotherTime;
|
if (sa.hasParam("AICheckSVar")) {
|
||||||
|
final String svarToCheck = sa.getParam("AICheckSVar");
|
||||||
|
String comparator = "GE";
|
||||||
|
int compareTo = 1;
|
||||||
|
|
||||||
|
if (sa.hasParam("AISVarCompare")) {
|
||||||
|
final String fullCmp = sa.getParam("AISVarCompare");
|
||||||
|
comparator = fullCmp.substring(0, 2);
|
||||||
|
final String strCmpTo = fullCmp.substring(2);
|
||||||
|
try {
|
||||||
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
|
} catch (final Exception ignored) {
|
||||||
|
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
|
||||||
|
if (!Expressions.compare(left, comparator, compareTo)) {
|
||||||
|
return AiPlayDecision.AnotherTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is the "heaviest" check, which also sets up targets, defines X, etc.
|
// this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||||
@@ -906,7 +924,7 @@ public class AiController {
|
|||||||
|
|
||||||
// check if enough left (pass memory indirectly because we don't want to include those)
|
// check if enough left (pass memory indirectly because we don't want to include those)
|
||||||
Set<Card> tappedForMana = AiCardMemory.getMemorySet(player, MemorySet.PAYS_TAP_COST);
|
Set<Card> tappedForMana = AiCardMemory.getMemorySet(player, MemorySet.PAYS_TAP_COST);
|
||||||
if (tappedForMana != null && !tappedForMana.isEmpty() &&
|
if (tappedForMana != null && tappedForMana.isEmpty() &&
|
||||||
!ComputerUtilCost.checkTapTypeCost(player, sa.getPayCosts(), host, sa, new CardCollection(tappedForMana))) {
|
!ComputerUtilCost.checkTapTypeCost(player, sa.getPayCosts(), host, sa, new CardCollection(tappedForMana))) {
|
||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
}
|
}
|
||||||
@@ -1646,9 +1664,6 @@ public class AiController {
|
|||||||
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case of infinite loop reset below would not be reached
|
|
||||||
timeoutReached = false;
|
|
||||||
|
|
||||||
FutureTask<SpellAbility> future = new FutureTask<>(() -> {
|
FutureTask<SpellAbility> future = new FutureTask<>(() -> {
|
||||||
//avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast...
|
//avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast...
|
||||||
boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0);
|
boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0);
|
||||||
@@ -1658,11 +1673,6 @@ public class AiController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeoutReached) {
|
|
||||||
timeoutReached = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.getHostCard().hasKeyword(Keyword.STORM)
|
if (sa.getHostCard().hasKeyword(Keyword.STORM)
|
||||||
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
||||||
&& player.getZone(ZoneType.Hand).contains(
|
&& player.getZone(ZoneType.Hand).contains(
|
||||||
@@ -1742,10 +1752,7 @@ public class AiController {
|
|||||||
t.stop();
|
t.stop();
|
||||||
} catch (UnsupportedOperationException ex) {
|
} catch (UnsupportedOperationException ex) {
|
||||||
// Android and Java 20 dropped support to stop so sadly thread will keep running
|
// Android and Java 20 dropped support to stop so sadly thread will keep running
|
||||||
timeoutReached = true;
|
|
||||||
future.cancel(true);
|
future.cancel(true);
|
||||||
// TODO wait a few more seconds to try and exit at a safe point before letting the engine continue
|
|
||||||
// TODO mark some as skipped to increase chance to find something playable next priority
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1798,9 +1805,14 @@ public class AiController {
|
|||||||
* @param sa the sa
|
* @param sa the sa
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public final boolean aiShouldRun(final CardTraitBase effect, final SpellAbility sa, final Card host, final GameEntity affected) {
|
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) {
|
||||||
|
Card hostCard = effect.getHostCard();
|
||||||
|
if (hostCard.hasAlternateState()) {
|
||||||
|
hostCard = game.getCardState(hostCard);
|
||||||
|
}
|
||||||
|
|
||||||
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
|
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
|
||||||
final Player controller = host.getController();
|
final Player controller = hostCard.getController();
|
||||||
if (affected instanceof Player) {
|
if (affected instanceof Player) {
|
||||||
return !((Player) affected).isOpponentOf(controller);
|
return !((Player) affected).isOpponentOf(controller);
|
||||||
}
|
}
|
||||||
@@ -1809,6 +1821,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (effect.hasParam("AICheckSVar")) {
|
if (effect.hasParam("AICheckSVar")) {
|
||||||
|
System.out.println("aiShouldRun?" + sa);
|
||||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
final String svarToCheck = effect.getParam("AICheckSVar");
|
||||||
String comparator = "GE";
|
String comparator = "GE";
|
||||||
int compareTo = 1;
|
int compareTo = 1;
|
||||||
@@ -1821,9 +1834,9 @@ public class AiController {
|
|||||||
compareTo = Integer.parseInt(strCmpTo);
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), effect);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
|
||||||
} else {
|
} else {
|
||||||
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1831,12 +1844,13 @@ public class AiController {
|
|||||||
int left = 0;
|
int left = 0;
|
||||||
|
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
left = AbilityUtils.calculateAmount(host, svarToCheck, effect);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
|
||||||
} else {
|
} else {
|
||||||
left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||||
}
|
}
|
||||||
|
System.out.println("aiShouldRun?" + left + comparator + compareTo);
|
||||||
return Expressions.compare(left, comparator, compareTo);
|
return Expressions.compare(left, comparator, compareTo);
|
||||||
} else if (effect.isKeyword(Keyword.DREDGE)) {
|
} else if (effect.hasParam("AICheckDredge")) {
|
||||||
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
||||||
} else return sa != null && doTrigger(sa, false);
|
} else return sa != null && doTrigger(sa, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,15 +29,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
private final CardCollection tapped;
|
private final CardCollection tapped;
|
||||||
|
|
||||||
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect) {
|
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect) {
|
||||||
this(ai0, sa, effect, false);
|
|
||||||
}
|
|
||||||
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect, final boolean payMana) {
|
|
||||||
super(ai0, effect, sa, sa.getHostCard());
|
super(ai0, effect, sa, sa.getHostCard());
|
||||||
|
|
||||||
discarded = new CardCollection();
|
discarded = new CardCollection();
|
||||||
tapped = new CardCollection();
|
tapped = new CardCollection();
|
||||||
Set<Card> tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST);
|
Set<Card> tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST);
|
||||||
if (!payMana && tappedForMana != null) {
|
if (tappedForMana != null) {
|
||||||
tapped.addAll(tappedForMana);
|
tapped.addAll(tappedForMana);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +110,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
||||||
}
|
}
|
||||||
return PaymentDecision.card(randomSubset);
|
return PaymentDecision.card(randomSubset);
|
||||||
} else if (type.contains("+WithDifferentNames")) {
|
} else if (type.equals("DifferentNames")) {
|
||||||
CardCollection differentNames = new CardCollection();
|
CardCollection differentNames = new CardCollection();
|
||||||
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
||||||
while (c > 0) {
|
while (c > 0) {
|
||||||
@@ -566,7 +563,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
removed += thisRemove;
|
removed += thisRemove;
|
||||||
table.put(null, prefCard, cType, thisRemove);
|
table.put(null, prefCard, CounterType.get(cType), thisRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,7 +573,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||||
final int c = cost.getAbilityAmount(ability);
|
final int c = cost.getAbilityAmount(ability);
|
||||||
final Card originalHost = ObjectUtils.getIfNull(ability.getOriginalHost(), source);
|
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
|
||||||
|
|
||||||
if (c <= 0) {
|
if (c <= 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -719,7 +716,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
toRemove += over;
|
||||||
table.put(null, crd, CounterEnumType.QUEST, over);
|
table.put(null, crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -767,7 +767,7 @@ public class ComputerUtil {
|
|||||||
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
|
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
|
||||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
||||||
|
|
||||||
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterEnumType.STUN));
|
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
|
||||||
|
|
||||||
if (untap) {
|
if (untap) {
|
||||||
typeList.remove(activate);
|
typeList.remove(activate);
|
||||||
@@ -2542,7 +2542,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
boolean opponent = controller.isOpponentOf(ai);
|
boolean opponent = controller.isOpponentOf(ai);
|
||||||
|
|
||||||
final CounterType p1p1Type = CounterEnumType.P1P1;
|
final CounterType p1p1Type = CounterType.get(CounterEnumType.P1P1);
|
||||||
|
|
||||||
if (!sa.hasParam("AILogic")) {
|
if (!sa.hasParam("AILogic")) {
|
||||||
return Aggregates.random(options);
|
return Aggregates.random(options);
|
||||||
@@ -3104,38 +3104,41 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) {
|
public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
if (source == null || !sa.hasParam("AITgts")) {
|
if (source == null) { return srcList; }
|
||||||
return srcList;
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollection list;
|
if (sa.hasParam("AITgts")) {
|
||||||
String aiTgts = sa.getParam("AITgts");
|
CardCollection list;
|
||||||
if (aiTgts.startsWith("BetterThan")) {
|
String aiTgts = sa.getParam("AITgts");
|
||||||
int value = 0;
|
if (aiTgts.startsWith("BetterThan")) {
|
||||||
if (aiTgts.endsWith("Source")) {
|
int value = 0;
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
if (aiTgts.endsWith("Source")) {
|
||||||
if (source.isEnchanted()) {
|
value = ComputerUtilCard.evaluateCreature(source);
|
||||||
for (Card enc : source.getEnchantedBy()) {
|
if (source.isEnchanted()) {
|
||||||
if (enc.getController().equals(ai)) {
|
for (Card enc : source.getEnchantedBy()) {
|
||||||
value += 100; // is 100 per AI's own aura enough?
|
if (enc.getController().equals(ai)) {
|
||||||
|
value += 100; // is 100 per AI's own aura enough?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (aiTgts.contains("EvalRating.")) {
|
||||||
|
value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa);
|
||||||
|
} else {
|
||||||
|
System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa);
|
||||||
|
value = ComputerUtilCard.evaluateCreature(source);
|
||||||
}
|
}
|
||||||
} else if (aiTgts.contains("EvalRating.")) {
|
final int totalValue = value;
|
||||||
value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa);
|
list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30);
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa);
|
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa);
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
}
|
||||||
|
|
||||||
|
if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) {
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return srcList;
|
||||||
}
|
}
|
||||||
final int totalValue = value;
|
|
||||||
list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30);
|
|
||||||
} else {
|
|
||||||
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
return srcList;
|
return srcList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -974,13 +974,17 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pBonus = 0;
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumAtt")) {
|
if (!ability.hasParam("NumAtt")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||||
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||||
|
if (pBonus > 0) {
|
||||||
|
power += pBonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -994,11 +998,12 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||||
}
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||||
|
if (pBonus > 0) {
|
||||||
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
power += pBonus;
|
||||||
power += pBonus;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,13 +1107,17 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int tBonus = 0;
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumDef")) {
|
if (!ability.hasParam("NumDef")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||||
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||||
|
if (tBonus > 0) {
|
||||||
|
toughness += tBonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1122,11 +1131,12 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||||
}
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||||
|
if (tBonus > 0) {
|
||||||
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
toughness += tBonus;
|
||||||
toughness += tBonus;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toughness;
|
return toughness;
|
||||||
@@ -1295,7 +1305,6 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pBonus = 0;
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumAtt")) {
|
if (!ability.hasParam("NumAtt")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1305,8 +1314,11 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost()) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||||
|
if (pBonus > 0) {
|
||||||
|
power += pBonus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
@@ -1321,14 +1333,13 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost()) {
|
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||||
|
if (pBonus > 0) {
|
||||||
|
power += pBonus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
|
||||||
power += pBonus;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
@@ -1519,14 +1530,16 @@ public class ComputerUtilCombat {
|
|||||||
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
|
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int tBonus = 0;
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumDef")) {
|
if (!ability.hasParam("NumDef")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
toughness += AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1540,11 +1553,10 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||||
}
|
if (tBonus > 0) {
|
||||||
|
toughness += tBonus;
|
||||||
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
}
|
||||||
toughness += tBonus;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toughness;
|
return toughness;
|
||||||
|
|||||||
@@ -287,9 +287,7 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int amount = ma.hasParam("Amount") ? AbilityUtils.calculateAmount(ma.getHostCard(), ma.getParam("Amount"), ma) : 1;
|
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) {
|
||||||
if (amount <= 0) {
|
|
||||||
// wrong gamestate for variable amount
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,14 +351,9 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// these should come last since they reserve the paying cards
|
|
||||||
// (this means if a mana ability has both parts it doesn't currently undo reservations if the second part fails)
|
|
||||||
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
|
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return paymentChoice;
|
return paymentChoice;
|
||||||
}
|
}
|
||||||
@@ -450,6 +443,7 @@ public class ComputerUtilMana {
|
|||||||
manaProduced = manaProduced.replace(s, color);
|
manaProduced = manaProduced.replace(s, color);
|
||||||
}
|
}
|
||||||
} else if (saMana.hasParam("ReplaceColor")) {
|
} else if (saMana.hasParam("ReplaceColor")) {
|
||||||
|
// replace color
|
||||||
String color = saMana.getParam("ReplaceColor");
|
String color = saMana.getParam("ReplaceColor");
|
||||||
if ("Chosen".equals(color)) {
|
if ("Chosen".equals(color)) {
|
||||||
if (card.hasChosenColor()) {
|
if (card.hasChosenColor()) {
|
||||||
@@ -741,8 +735,7 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
||||||
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
|
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
|
||||||
// not a good idea to sac a card that you're targeting with the SA you're paying for
|
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||||
saExcludeList.add(saPayment);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -816,11 +809,11 @@ public class ComputerUtilMana {
|
|||||||
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove to prevent re-usage since resources don't get consumed
|
// remove from available lists
|
||||||
sourcesForShards.values().removeIf(CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
sourcesForShards.values().removeIf(CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
||||||
} else {
|
} else {
|
||||||
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||||
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect, true))) {
|
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect))) {
|
||||||
saList.remove(saPayment);
|
saList.remove(saPayment);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -829,10 +822,8 @@ public class ComputerUtilMana {
|
|||||||
// subtract mana from mana pool
|
// subtract mana from mana pool
|
||||||
manapool.payManaFromAbility(sa, cost, saPayment);
|
manapool.payManaFromAbility(sa, cost, saPayment);
|
||||||
|
|
||||||
// need to consider if another use is now prevented
|
// no need to remove abilities from resource map,
|
||||||
if (!cost.isPaid() && saPayment.isActivatedAbility() && !saPayment.getRestrictions().canPlay(saPayment.getHostCard(), saPayment)) {
|
// once their costs are paid and consume resources, they can not be used again
|
||||||
sourcesForShards.values().removeIf(s -> s == saPayment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasConverge) {
|
if (hasConverge) {
|
||||||
// hack to prevent converge re-using sources
|
// hack to prevent converge re-using sources
|
||||||
@@ -1505,7 +1496,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cost.isReusuableResource()) {
|
if (!cost.isReusuableResource()) {
|
||||||
for (CostPart part : cost.getCostParts()) {
|
for(CostPart part : cost.getCostParts()) {
|
||||||
if (part instanceof CostSacrifice && !part.payCostFromSource()) {
|
if (part instanceof CostSacrifice && !part.payCostFromSource()) {
|
||||||
unpreferredCost = true;
|
unpreferredCost = true;
|
||||||
}
|
}
|
||||||
@@ -1596,8 +1587,10 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
// don't use abilities with dangerous drawbacks
|
// don't use abilities with dangerous drawbacks
|
||||||
AbilitySub sub = m.getSubAbility();
|
AbilitySub sub = m.getSubAbility();
|
||||||
if (sub != null && !SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
if (sub != null) {
|
||||||
continue;
|
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
||||||
@@ -1665,6 +1658,7 @@ public class ComputerUtilMana {
|
|||||||
if (replaced.contains("C")) {
|
if (replaced.contains("C")) {
|
||||||
manaMap.put(ManaAtom.COLORLESS, m);
|
manaMap.put(ManaAtom.COLORLESS, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,11 +460,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
||||||
Card host = replacementEffect.getHostCard();
|
return brains.aiShouldRun(replacementEffect, effectSA, affected);
|
||||||
if (host.hasAlternateState()) {
|
|
||||||
host = host.getGame().getCardState(host);
|
|
||||||
}
|
|
||||||
return brains.aiShouldRun(replacementEffect, effectSA, host, affected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1351,11 +1347,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
// Ai won't understand that anyway
|
// Ai won't understand that anyway
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
|
|
||||||
// Ai won't understand that anyway
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<DeckSection, List<? extends PaperCard>> complainCardsCantPlayWell(Deck myDeck) {
|
public Map<DeckSection, List<? extends PaperCard>> complainCardsCantPlayWell(Deck myDeck) {
|
||||||
// TODO check if profile detection set to Auto
|
// TODO check if profile detection set to Auto
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ public class SpecialAiLogic {
|
|||||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
||||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||||
|
|
||||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterEnumType.POISON)) {
|
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ public class SpecialAiLogic {
|
|||||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
||||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||||
|
|
||||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterEnumType.POISON)) {
|
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiLogic.equals("SurpriseBlock")) {
|
if (aiLogic.equals("BeforeCombat")) {
|
||||||
|
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (aiLogic.equals("SurpriseBlock")) {
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -294,7 +298,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
try {
|
||||||
|
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// This happens when Origin is something like
|
||||||
|
// "Graveyard,Library" (Doomsday)
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final String destination = sa.getParam("Destination");
|
final String destination = sa.getParam("Destination");
|
||||||
|
|
||||||
@@ -489,6 +499,19 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// Fetching should occur fairly often as it helps cast more spells, and
|
// Fetching should occur fairly often as it helps cast more spells, and
|
||||||
// have access to more mana
|
// have access to more mana
|
||||||
|
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
if (sa.getParam("AILogic").equals("Never")) {
|
||||||
|
/*
|
||||||
|
* Hack to stop AI from using Aviary Mechanic's "may bounce" trigger.
|
||||||
|
* Ideally it should look for a good bounce target like "Pacifism"-victims
|
||||||
|
* but there is no simple way to check that. It is preferable for the AI
|
||||||
|
* to make sub-optimal choices (waste bounce) than to make obvious mistakes
|
||||||
|
* (bounce useful permanent).
|
||||||
|
*/
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<ZoneType> origin = new ArrayList<>();
|
List<ZoneType> origin = new ArrayList<>();
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||||
@@ -761,8 +784,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (aiLogic.equals("BeforeCombat")) {
|
|
||||||
return !ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
@@ -889,6 +910,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||||
|
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||||
|
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
|
||||||
|
list = CardLists.filter(list, card -> ComputerUtilCard.evaluateCreature(card) > ComputerUtilCard.evaluateCreature(source) + 30);
|
||||||
|
}
|
||||||
|
|
||||||
if (source.isInZone(ZoneType.Hand)) {
|
if (source.isInZone(ZoneType.Hand)) {
|
||||||
list = CardLists.filter(list, CardPredicates.nameNotEquals(source.getName())); // Don't get the same card back.
|
list = CardLists.filter(list, CardPredicates.nameNotEquals(source.getName())); // Don't get the same card back.
|
||||||
@@ -897,6 +921,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
if (sa.hasParam("AttachedTo")) {
|
if (sa.hasParam("AttachedTo")) {
|
||||||
list = CardLists.filter(list, c -> {
|
list = CardLists.filter(list, c -> {
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
@@ -1239,12 +1265,53 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if max CMC exceeded, do not choose this card (but keep looking for other options)
|
||||||
|
if (sa.hasParam("MaxTotalTargetCMC")) {
|
||||||
|
if (choice.getCMC() > sa.getTargetRestrictions().getMaxTotalCMC(choice, sa) - sa.getTargets().getTotalTargetedCMC()) {
|
||||||
|
list.remove(choice);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if max power exceeded, do not choose this card (but keep looking for other options)
|
||||||
|
if (sa.hasParam("MaxTotalTargetPower")) {
|
||||||
|
if (choice.getNetPower() > sa.getTargetRestrictions().getMaxTotalPower(choice, sa) -sa.getTargets().getTotalTargetedPower()) {
|
||||||
|
list.remove(choice);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// honor the Same Creature Type restriction
|
||||||
|
if (sa.getTargetRestrictions().isWithSameCreatureType()) {
|
||||||
|
Card firstTarget = sa.getTargetCard();
|
||||||
|
if (firstTarget != null && !choice.sharesCreatureTypeWith(firstTarget)) {
|
||||||
|
list.remove(choice);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
if (sa.canTarget(choice)) {
|
if (sa.canTarget(choice)) {
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Honor the Single Zone restriction. For now, simply remove targets that do not belong to the same zone as the first targeted card.
|
||||||
|
// TODO: ideally the AI should consider at this point which targets exactly to pick (e.g. one card in the first player's graveyard
|
||||||
|
// vs. two cards in the second player's graveyard, which cards are more relevant to be targeted, etc.). Consider improving.
|
||||||
|
if (sa.getTargetRestrictions().isSingleZone()) {
|
||||||
|
Card firstTgt = sa.getTargetCard();
|
||||||
|
CardCollection toRemove = new CardCollection();
|
||||||
|
if (firstTgt != null) {
|
||||||
|
for (Card t : sa.getTargets().getTargetCards()) {
|
||||||
|
if (!t.getController().equals(firstTgt.getController())) {
|
||||||
|
toRemove.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sa.getTargets().removeAll(toRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1540,7 +1607,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
} else if (logic.startsWith("ExilePreference")) {
|
} else if (logic.startsWith("ExilePreference")) {
|
||||||
return doExilePreferenceLogic(decider, sa, fetchList);
|
return doExilePreferenceLogic(decider, sa, fetchList);
|
||||||
} else if (logic.equals("BounceOwnTrigger")) {
|
} else if (logic.equals("BounceOwnTrigger")) {
|
||||||
return doBounceOwnTriggerLogic(decider, sa, fetchList);
|
return doBounceOwnTriggerLogic(decider, fetchList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fetchList.isEmpty()) {
|
if (fetchList.isEmpty()) {
|
||||||
@@ -2104,18 +2171,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.BOUNCED_THIS_TURN);
|
return AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.BOUNCED_THIS_TURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Card doBounceOwnTriggerLogic(Player ai, SpellAbility sa, CardCollection choices) {
|
private static Card doBounceOwnTriggerLogic(Player ai, CardCollection choices) {
|
||||||
CardCollection unprefChoices = CardLists.filter(choices, c -> !c.isToken() && c.getOwner().equals(ai));
|
CardCollection unprefChoices = CardLists.filter(choices, c -> !c.isToken() && c.getOwner().equals(ai));
|
||||||
// TODO check for threatened cards
|
|
||||||
CardCollection prefChoices = CardLists.filter(unprefChoices, c -> c.hasETBTrigger(false));
|
CardCollection prefChoices = CardLists.filter(unprefChoices, c -> c.hasETBTrigger(false));
|
||||||
if (!prefChoices.isEmpty()) {
|
if (!prefChoices.isEmpty()) {
|
||||||
return ComputerUtilCard.getBestAI(prefChoices);
|
return ComputerUtilCard.getBestAI(prefChoices);
|
||||||
}
|
} else if (!unprefChoices.isEmpty()) {
|
||||||
if (!unprefChoices.isEmpty() && sa.getSubAbility() != null) {
|
|
||||||
// some extra benefit like First Responder
|
|
||||||
return ComputerUtilCard.getWorstAI(unprefChoices);
|
return ComputerUtilCard.getWorstAI(unprefChoices);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -96,10 +96,6 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
chance = cloneTgtAI(sa);
|
chance = cloneTgtAI(sa);
|
||||||
} else {
|
} else {
|
||||||
if (sa.isReplacementAbility() && host.isCloned()) {
|
|
||||||
// prevent StackOverflow from infinite loop copying another ETB RE
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
|
||||||
}
|
|
||||||
if (sa.hasParam("Choices")) {
|
if (sa.hasParam("Choices")) {
|
||||||
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
|
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
sa.getParam("Choices"), host.getController(), host, sa);
|
sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
@@ -192,7 +188,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
||||||
|
|
||||||
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||||
: "Permanent.YouDontCtrl+!named" + name + ",Permanent.nonLegendary+!named" + name;
|
: "Permanent.YouDontCtrl+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name;
|
||||||
|
|
||||||
// TODO: rewrite this block so that this is done somehow more elegantly
|
// TODO: rewrite this block so that this is done somehow more elegantly
|
||||||
if (canCloneLegendary) {
|
if (canCloneLegendary) {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(
|
return new AiAbilityDecision(
|
||||||
sa.isTargetNumberValid() ? 100 : 0,
|
sa.isTargetNumberValid() && !sa.getTargets().isEmpty() ? 100 : 0,
|
||||||
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,15 +53,17 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (mandatory) {
|
|
||||||
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
|
||||||
if (sa.isTargetNumberValid()) {
|
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
return decision;
|
|
||||||
} else {
|
} else {
|
||||||
return canPlay(aiPlayer, sa);
|
if (mandatory) {
|
||||||
|
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
||||||
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decision;
|
||||||
|
} else {
|
||||||
|
return canPlay(aiPlayer, sa);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
// Not at EOT phase
|
// Not at EOT phase
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
}
|
}
|
||||||
} else if ("DuplicatePerms".equals(aiLogic)) {
|
} if ("DuplicatePerms".equals(aiLogic)) {
|
||||||
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
if (valid.size() < 2) {
|
if (valid.size() < 2) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
@@ -212,7 +212,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public abstract class CountersAi extends SpellAbilityAi {
|
|||||||
} else if (type.equals("DIVINITY")) {
|
} else if (type.equals("DIVINITY")) {
|
||||||
final CardCollection boon = CardLists.filter(list, c -> c.getCounters(CounterEnumType.DIVINITY) == 0);
|
final CardCollection boon = CardLists.filter(list, c -> c.getCounters(CounterEnumType.DIVINITY) == 0);
|
||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon);
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon);
|
||||||
} else if (CounterType.getType(type).isKeywordCounter()) {
|
} else if (CounterType.get(type).isKeywordCounter()) {
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(CardLists.getNotKeyword(list, type));
|
choice = ComputerUtilCard.getBestCreatureAI(CardLists.getNotKeyword(list, type));
|
||||||
} else {
|
} else {
|
||||||
// The AI really should put counters on cards that can use it.
|
// The AI really should put counters on cards that can use it.
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (counterType == null || counterType.is(type)) {
|
if (counterType == null || counterType.is(type)) {
|
||||||
addTargetsByCounterType(ai, sa, aiList, type);
|
addTargetsByCounterType(ai, sa, aiList, CounterType.get(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
if (!oppList.isEmpty()) {
|
if (!oppList.isEmpty()) {
|
||||||
// not enough targets
|
// not enough targets
|
||||||
if (sa.canAddMoreTarget()) {
|
if (sa.canAddMoreTarget()) {
|
||||||
final CounterType type = CounterEnumType.M1M1;
|
final CounterType type = CounterType.get(CounterEnumType.M1M1);
|
||||||
if (counterType == null || counterType == type) {
|
if (counterType == null || counterType == type) {
|
||||||
addTargetsByCounterType(ai, sa, oppList, type);
|
addTargetsByCounterType(ai, sa, oppList, type);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
// Proliferate is always optional for all, no need to select best
|
// Proliferate is always optional for all, no need to select best
|
||||||
|
|
||||||
final CounterType poison = CounterEnumType.POISON;
|
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
||||||
|
|
||||||
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
||||||
// because countertype can't be chosen anymore, only look for poison counters
|
// because countertype can't be chosen anymore, only look for poison counters
|
||||||
|
|||||||
@@ -92,8 +92,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return chance > MyRandom.getRandom().nextFloat();
|
return chance > MyRandom.getRandom().nextFloat();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isKeyword(Keyword.LEVEL_UP)) {
|
if (sa.isKeyword(Keyword.LEVEL_UP)) {
|
||||||
@@ -123,6 +124,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
|
CardCollection list;
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.isDividedAsYouChoose();
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
@@ -168,7 +170,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||||
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
||||||
|
|
||||||
oppCreatM1 = CardLists.filter(oppCreatM1, input -> input.getNetToughness() <= 1 && input.canReceiveCounters(CounterEnumType.M1M1));
|
oppCreatM1 = CardLists.filter(oppCreatM1, input -> input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1)));
|
||||||
|
|
||||||
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
||||||
if (best != null) {
|
if (best != null) {
|
||||||
@@ -290,8 +292,10 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
if (willActivate) {
|
if (willActivate) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
} else if (logic.equals("ChargeToBestCMC")) {
|
} else if (logic.equals("ChargeToBestCMC")) {
|
||||||
return doChargeToCMCLogic(ai, sa);
|
return doChargeToCMCLogic(ai, sa);
|
||||||
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
||||||
@@ -332,7 +336,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
|
|
||||||
if (!source.canReceiveCounters(CounterEnumType.P1P1) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return doCombatAdaptLogic(source, amount, combat);
|
return doCombatAdaptLogic(source, amount, combat);
|
||||||
@@ -344,7 +348,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
return FightAi.canFight(ai, sa, nPump, nPump);
|
return FightAi.canFightAi(ai, sa, nPump, nPump);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amountStr.equals("X")) {
|
if (amountStr.equals("X")) {
|
||||||
@@ -438,16 +442,17 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
sa.addDividedAllocation(c, amount);
|
sa.addDividedAllocation(c, amount);
|
||||||
return decision;
|
return decision;
|
||||||
} else if (!hasSacCost) {
|
} else {
|
||||||
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
if (!hasSacCost) {
|
||||||
return decision;
|
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list;
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
@@ -603,21 +608,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int currCounters = cards.get(0).getCounters(CounterType.getType(type));
|
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
||||||
|
|
||||||
// adding counters would cause counter amount to overflow
|
|
||||||
if (Integer.MAX_VALUE - currCounters <= amount) {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
if (type.equals("P1P1")) {
|
|
||||||
if (Integer.MAX_VALUE - cards.get(0).getNetPower() <= amount) {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
if (Integer.MAX_VALUE - cards.get(0).getNetToughness() <= amount) {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// each non +1/+1 counter on the card is a 10% chance of not
|
// each non +1/+1 counter on the card is a 10% chance of not
|
||||||
// activating this ability.
|
// activating this ability.
|
||||||
|
|
||||||
@@ -632,7 +623,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Useless since the card already has the keyword (or for another reason)
|
// Useless since the card already has the keyword (or for another reason)
|
||||||
if (ComputerUtil.isUselessCounter(CounterType.getType(type), cards.get(0))) {
|
if (ComputerUtil.isUselessCounter(CounterType.get(type), cards.get(0))) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -679,12 +670,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
CardCollection list;
|
CardCollection list = null;
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
}
|
}
|
||||||
|
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
|
||||||
if (list.isEmpty() && isMandatoryTrigger) {
|
if (list.isEmpty() && isMandatoryTrigger) {
|
||||||
@@ -700,8 +693,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|| sa.getTargets().isEmpty()) {
|
|| sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
@@ -743,7 +737,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String aiLogic = sa.getParam("AILogic");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
boolean preferred = true;
|
||||||
|
CardCollection list;
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.isDividedAsYouChoose();
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
@@ -762,10 +758,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("ChargeToBestCMC".equals(aiLogic)) {
|
if ("ChargeToBestCMC".equals(aiLogic)) {
|
||||||
if (mandatory) {
|
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
|
} else if (mandatory) {
|
||||||
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return doChargeToCMCLogic(ai, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
@@ -789,6 +789,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// things like Powder Keg, which are way too complex for the AI
|
// things like Powder Keg, which are way too complex for the AI
|
||||||
}
|
}
|
||||||
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
||||||
|
// can only target opponent
|
||||||
PlayerCollection playerList = new PlayerCollection(IterableUtil.filter(
|
PlayerCollection playerList = new PlayerCollection(IterableUtil.filter(
|
||||||
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
||||||
|
|
||||||
@@ -803,32 +804,34 @@ public class CountersPutAi extends CountersAi {
|
|||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ("Fight".equals(aiLogic) || "PowerDmg".equals(aiLogic)) {
|
String logic = sa.getParam("AILogic");
|
||||||
|
if ("Fight".equals(logic) || "PowerDmg".equals(logic)) {
|
||||||
int nPump = 0;
|
int nPump = 0;
|
||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
AiAbilityDecision decision = FightAi.canFight(ai, sa, nPump, nPump);
|
AiAbilityDecision decision = FightAi.canFightAi(ai, sa, nPump, nPump);
|
||||||
if (decision.willingToPlay()) {
|
if (decision.willingToPlay()) {
|
||||||
return decision;
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
|
||||||
|
|
||||||
Iterable<Card> filteredField;
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
filteredField = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
filteredField = ai.getCardsIn(ZoneType.Battlefield);
|
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
}
|
}
|
||||||
CardCollection list = CardLists.getTargetableCards(filteredField, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
|
||||||
int totalTargets = list.size();
|
|
||||||
boolean preferred = true;
|
|
||||||
|
|
||||||
|
// Filter AI-specific targets if provided
|
||||||
|
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||||
|
|
||||||
|
int totalTargets = list.size();
|
||||||
|
|
||||||
|
sa.resetTargets();
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
|
// When things are mandatory, gotta handle a little differently
|
||||||
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
@@ -853,7 +856,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice;
|
Card choice = null;
|
||||||
|
|
||||||
// Choose targets here:
|
// Choose targets here:
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
@@ -862,27 +865,33 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (choice == null && mandatory) {
|
if (choice == null && mandatory) {
|
||||||
choice = Aggregates.random(list);
|
choice = Aggregates.random(list);
|
||||||
}
|
}
|
||||||
} else if (type.equals("M1M1")) {
|
|
||||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
|
||||||
} else {
|
} else {
|
||||||
choice = Aggregates.random(list);
|
if (type.equals("M1M1")) {
|
||||||
|
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
|
} else {
|
||||||
|
choice = Aggregates.random(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (preferred) {
|
|
||||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
|
||||||
choice = chooseBoonTarget(list, type);
|
|
||||||
if (choice == null && mandatory) {
|
|
||||||
choice = Aggregates.random(list);
|
|
||||||
}
|
|
||||||
} else if (type.equals("P1P1")) {
|
|
||||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
|
||||||
} else {
|
} else {
|
||||||
choice = Aggregates.random(list);
|
if (preferred) {
|
||||||
|
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||||
|
choice = chooseBoonTarget(list, type);
|
||||||
|
if (choice == null && mandatory) {
|
||||||
|
choice = Aggregates.random(list);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (type.equals("P1P1")) {
|
||||||
|
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
|
} else {
|
||||||
|
choice = Aggregates.random(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (choice != null && divided) {
|
if (choice != null && divided) {
|
||||||
|
int alloc = Math.max(amount / totalTargets, 1);
|
||||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||||
sa.addDividedAllocation(choice, left);
|
sa.addDividedAllocation(choice, left);
|
||||||
} else {
|
} else {
|
||||||
int alloc = Math.max(amount / totalTargets, 1);
|
|
||||||
sa.addDividedAllocation(choice, alloc);
|
sa.addDividedAllocation(choice, alloc);
|
||||||
left -= alloc;
|
left -= alloc;
|
||||||
}
|
}
|
||||||
@@ -952,8 +961,8 @@ public class CountersPutAi extends CountersAi {
|
|||||||
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
// Bolster does use this
|
// Bolster does use this
|
||||||
// TODO need more or less logic there?
|
// TODO need more or less logic there?
|
||||||
final CounterType m1m1 = CounterEnumType.M1M1;
|
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||||
final CounterType p1p1 = CounterEnumType.P1P1;
|
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||||
|
|
||||||
// no logic if there is no options or no to choice
|
// no logic if there is no options or no to choice
|
||||||
if (!isOptional && Iterables.size(options) <= 1) {
|
if (!isOptional && Iterables.size(options) <= 1) {
|
||||||
@@ -972,7 +981,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
final boolean isCurse = sa.isCurse();
|
||||||
|
|
||||||
|
if (isCurse) {
|
||||||
final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents());
|
final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents());
|
||||||
|
|
||||||
if (!opponents.isEmpty()) {
|
if (!opponents.isEmpty()) {
|
||||||
@@ -1069,10 +1080,11 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Player ai = sa.getActivatingPlayer();
|
Player ai = sa.getActivatingPlayer();
|
||||||
GameEntity e = (GameEntity) params.get("Target");
|
GameEntity e = (GameEntity) params.get("Target");
|
||||||
// for Card try to select not useless counter
|
// for Card try to select not useless counter
|
||||||
if (e instanceof Card c) {
|
if (e instanceof Card) {
|
||||||
|
Card c = (Card) e;
|
||||||
if (c.getController().isOpponentOf(ai)) {
|
if (c.getController().isOpponentOf(ai)) {
|
||||||
if (options.contains(CounterEnumType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||||
return CounterEnumType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (ComputerUtil.isNegativeCounter(type, c)) {
|
if (ComputerUtil.isNegativeCounter(type, c)) {
|
||||||
@@ -1086,14 +1098,15 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (e instanceof Player p) {
|
} else if (e instanceof Player) {
|
||||||
|
Player p = (Player) e;
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
if (options.contains(CounterEnumType.POISON)) {
|
if (options.contains(CounterType.get(CounterEnumType.POISON))) {
|
||||||
return CounterEnumType.POISON;
|
return CounterType.get(CounterEnumType.POISON);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.contains(CounterEnumType.EXPERIENCE)) {
|
if (options.contains(CounterType.get(CounterEnumType.EXPERIENCE))) {
|
||||||
return CounterEnumType.EXPERIENCE;
|
return CounterType.get(CounterEnumType.EXPERIENCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1198,8 +1211,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
if (numCtrs < optimalCMC) {
|
if (numCtrs < optimalCMC) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
||||||
@@ -1219,8 +1233,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (numCtrs < optimalCMC) {
|
if (numCtrs < optimalCMC) {
|
||||||
// If the AI has less counters than the optimal CMC, it should play the ability.
|
// If the AI has less counters than the optimal CMC, it should play the ability.
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,18 +218,18 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
Card tgt = (Card) params.get("Target");
|
Card tgt = (Card) params.get("Target");
|
||||||
|
|
||||||
// planeswalker has high priority for loyalty counters
|
// planeswalker has high priority for loyalty counters
|
||||||
if (tgt.isPlaneswalker() && options.contains(CounterEnumType.LOYALTY)) {
|
if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
|
||||||
return CounterEnumType.LOYALTY;
|
return CounterType.get(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt.getController().isOpponentOf(ai)) {
|
if (tgt.getController().isOpponentOf(ai)) {
|
||||||
// creatures with BaseToughness below or equal zero might be
|
// creatures with BaseToughness below or equal zero might be
|
||||||
// killed if their counters are removed
|
// killed if their counters are removed
|
||||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||||
if (options.contains(CounterEnumType.P1P1)) {
|
if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||||
return CounterEnumType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
} else if (options.contains(CounterEnumType.M1M1)) {
|
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||||
return CounterEnumType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,17 +241,17 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// this counters are treat first to be removed
|
// this counters are treat first to be removed
|
||||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterEnumType.ICE)) {
|
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
|
||||||
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
||||||
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
|
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
|
||||||
|
|
||||||
if (maritEmpty) {
|
if (maritEmpty) {
|
||||||
return CounterEnumType.ICE;
|
return CounterType.get(CounterEnumType.ICE);
|
||||||
}
|
}
|
||||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterEnumType.P1P1)) {
|
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||||
return CounterEnumType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterEnumType.M1M1)) {
|
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||||
return CounterEnumType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback logic, select positive counter to add more
|
// fallback logic, select positive counter to add more
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (targetCard.getController().isOpponentOf(ai)) {
|
if (targetCard.getController().isOpponentOf(ai)) {
|
||||||
// if its a Planeswalker try to remove Loyality first
|
// if its a Planeswalker try to remove Loyality first
|
||||||
if (targetCard.isPlaneswalker()) {
|
if (targetCard.isPlaneswalker()) {
|
||||||
return CounterEnumType.LOYALTY;
|
return CounterType.get(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||||
@@ -392,10 +392,10 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.contains(CounterEnumType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||||
return CounterEnumType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
} else if (options.contains(CounterEnumType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
} else if (options.contains(CounterType.get(CounterEnumType.P1P1)) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||||
return CounterEnumType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||||
|
|||||||
@@ -133,9 +133,9 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
||||||
// When using Pestilence to hurt players, do it at
|
// When using Pestilence to hurt players, do it at
|
||||||
// the end of the opponent's turn only
|
// the end of the opponent's turn only
|
||||||
if (!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic"))
|
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|
||||||
|| (ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||||
&& !ai.getGame().getPhaseHandler().isPlayerTurn(ai)))
|
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
|
||||||
// Need further improvement : if able to kill immediately with repeated activations, do not wait
|
// Need further improvement : if able to kill immediately with repeated activations, do not wait
|
||||||
// for phases! Will also need to implement considering repeated activations for killed creatures!
|
// for phases! Will also need to implement considering repeated activations for killed creatures!
|
||||||
// || (ai.sa.getPayCosts(). ??? )
|
// || (ai.sa.getPayCosts(). ??? )
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.*;
|
import forge.game.cost.*;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -369,7 +370,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// try to make opponent lose to poison
|
// try to make opponent lose to poison
|
||||||
// currently only Caress of Phyrexia
|
// currently only Caress of Phyrexia
|
||||||
if (getPoison != null && oppA.canReceiveCounters(CounterEnumType.POISON)) {
|
if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
if (oppA.getPoisonCounters() + numCards > 9) {
|
if (oppA.getPoisonCounters() + numCards > 9) {
|
||||||
sa.getTargets().add(oppA);
|
sa.getTargets().add(oppA);
|
||||||
return true;
|
return true;
|
||||||
@@ -413,7 +414,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getPoison != null && ai.canReceiveCounters(CounterEnumType.POISON)) {
|
if (getPoison != null && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
if (numCards + ai.getPoisonCounters() >= 8) {
|
if (numCards + ai.getPoisonCounters() >= 8) {
|
||||||
aiTarget = false;
|
aiTarget = false;
|
||||||
}
|
}
|
||||||
@@ -471,7 +472,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ally would lose because of poison
|
// ally would lose because of poison
|
||||||
if (getPoison != null && ally.canReceiveCounters(CounterEnumType.POISON) && ally.getPoisonCounters() + numCards > 9) {
|
if (getPoison != null && ally.canReceiveCounters(CounterType.get(CounterEnumType.POISON)) && ally.getPoisonCounters() + numCards > 9) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("Fight")) {
|
} else if (logic.equals("Fight")) {
|
||||||
return FightAi.canFight(ai, sa, 0,0);
|
return FightAi.canFightAi(ai, sa, 0,0);
|
||||||
} else if (logic.equals("Pump")) {
|
} else if (logic.equals("Pump")) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import forge.ai.*;
|
|
||||||
import forge.game.Game;
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.Game;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.card.token.TokenInfo;
|
import forge.game.card.token.TokenInfo;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.CostPayLife;
|
|
||||||
import forge.game.phase.PhaseType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -34,26 +36,6 @@ public class EndureAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(bestCreature);
|
sa.getTargets().add(bestCreature);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Card-specific logic
|
|
||||||
final String num = sa.getParamOrDefault("Num", "1");
|
|
||||||
if ("X".equals(num) && sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
|
||||||
if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
|
||||||
}
|
|
||||||
int curLife = aiPlayer.getLife();
|
|
||||||
int dangerLife = (((PlayerControllerAi) aiPlayer.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
|
|
||||||
if (curLife <= dangerLife) {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
|
||||||
}
|
|
||||||
int availableMana = ComputerUtilMana.getAvailableManaEstimate(aiPlayer) - 1;
|
|
||||||
int maxEndureX = Math.min(availableMana, curLife - dangerLife);
|
|
||||||
if (maxEndureX > 0) {
|
|
||||||
sa.setXManaCostPaid(maxEndureX);
|
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
* @param power bonus to power
|
* @param power bonus to power
|
||||||
* @return true if fight effect should be played, false otherwise
|
* @return true if fight effect should be played, false otherwise
|
||||||
*/
|
*/
|
||||||
public static AiAbilityDecision canFight(final Player ai, final SpellAbility sa, int power, int toughness) {
|
public static AiAbilityDecision canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
AbilitySub tgtFight = sa.getSubAbility();
|
AbilitySub tgtFight = sa.getSubAbility();
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ import forge.game.spellability.SpellAbility;
|
|||||||
public class FlipACoinAi extends SpellAbilityAi {
|
public class FlipACoinAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#checkApiLogic(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String ailogic = sa.getParam("AILogic");
|
String ailogic = sa.getParam("AILogic");
|
||||||
if (ailogic.equals("PhaseOut")) {
|
if (ailogic.equals("Never")) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
} else if (ailogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class ManaAi extends SpellAbilityAi {
|
|||||||
int numCounters = 0;
|
int numCounters = 0;
|
||||||
int manaSurplus = 0;
|
int manaSurplus = 0;
|
||||||
if ("Count$xPaid".equals(host.getSVar("X")) && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
if ("Count$xPaid".equals(host.getSVar("X")) && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||||
CounterType ctrType = CounterEnumType.KI; // Petalmane Baku
|
CounterType ctrType = CounterType.get(CounterEnumType.KI); // Petalmane Baku
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
ctrType = ((CostRemoveCounter)part).counter;
|
ctrType = ((CostRemoveCounter)part).counter;
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
if (aiLogic.equals("LilianaMill")) {
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
|
||||||
|
if (aiLogic.equals("Main1")) {
|
||||||
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|
||||||
|
|| ComputerUtil.castSpellInMain1(ai, sa);
|
||||||
|
} else if (aiLogic.equals("LilianaMill")) {
|
||||||
// TODO convert to AICheckSVar
|
// TODO convert to AICheckSVar
|
||||||
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
||||||
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES).size() >= 1;
|
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES).size() >= 1;
|
||||||
@@ -50,10 +55,9 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
// because they are also potentially useful for combat
|
// because they are also potentially useful for combat
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
||||||
}
|
}
|
||||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|
return true;
|
||||||
|| ComputerUtil.castSpellInMain1(ai, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import forge.game.Game;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardUtil;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
@@ -38,6 +38,9 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
|
if (blocker == null) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
sa.getTargets().add(blocker);
|
sa.getTargets().add(blocker);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
@@ -60,6 +63,11 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
protected AiAbilityDecision doTriggerNoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
// only use on creatures that can attack
|
||||||
|
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
Card attacker = source;
|
Card attacker = source;
|
||||||
if (sa.hasParam("DefinedAttacker")) {
|
if (sa.hasParam("DefinedAttacker")) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
||||||
@@ -73,9 +81,13 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
final List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
||||||
if (list.isEmpty() && mandatory) {
|
if (list.isEmpty()) {
|
||||||
list = CardUtil.getValidCardsToTarget(sa);
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if (blocker == null) {
|
if (blocker == null) {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||||
&& ai.getManaPool().totalMana() <= 0
|
&& ai.getManaPool().totalMana() <= 0
|
||||||
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
|
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
|
||||||
&& !card.hasETBTrigger(true) && !card.hasSVar("AmbushAI")
|
&& (!card.hasETBTrigger(true) && !card.hasSVar("AmbushAI"))
|
||||||
&& game.getStack().isEmpty()
|
&& game.getStack().isEmpty()
|
||||||
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
|
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
|
||||||
// AiPlayDecision.AnotherTime;
|
// AiPlayDecision.AnotherTime;
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ public class PhasesAi extends SpellAbilityAi {
|
|||||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
|
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
|
||||||
if (isThreatened) {
|
if (isThreatened) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import forge.ai.ComputerUtil;
|
|||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.GameLossReason;
|
import forge.game.player.GameLossReason;
|
||||||
@@ -64,7 +65,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
boolean result;
|
boolean result;
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
result = tgtPlayer(ai, sa, mandatory);
|
result = tgtPlayer(ai, sa, mandatory);
|
||||||
} else if (mandatory || !ai.canReceiveCounters(CounterEnumType.POISON)) {
|
} else if (mandatory || !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
// mandatory or ai is uneffected
|
// mandatory or ai is uneffected
|
||||||
result = true;
|
result = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -89,7 +90,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
PlayerCollection betterTgts = tgts.filter(input -> {
|
PlayerCollection betterTgts = tgts.filter(input -> {
|
||||||
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (!input.canReceiveCounters(CounterEnumType.POISON)) {
|
} else if (!input.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -108,7 +109,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
if (tgts.isEmpty()) {
|
if (tgts.isEmpty()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
// AI is uneffected
|
// AI is uneffected
|
||||||
if (ai.canBeTargetedBy(sa) && !ai.canReceiveCounters(CounterEnumType.POISON)) {
|
if (ai.canBeTargetedBy(sa) && !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -120,7 +121,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return !input.canReceiveCounters(CounterEnumType.POISON);
|
return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
|
||||||
});
|
});
|
||||||
if (!betterAllies.isEmpty()) {
|
if (!betterAllies.isEmpty()) {
|
||||||
allies = betterAllies;
|
allies = betterAllies;
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFight) {
|
if (isFight) {
|
||||||
return FightAi.canFight(ai, sa, attack, defense).willingToPlay();
|
return FightAi.canFightAi(ai, sa, attack, defense).willingToPlay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import forge.ai.SpellAbilityAi;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.player.PlayerController;
|
import forge.game.player.PlayerController;
|
||||||
@@ -39,7 +40,7 @@ public class TimeTravelAi extends SpellAbilityAi {
|
|||||||
// so removing them is good; stuff on the battlefield is usually stuff like Vanishing or As Foretold, which favors adding Time
|
// so removing them is good; stuff on the battlefield is usually stuff like Vanishing or As Foretold, which favors adding Time
|
||||||
// counters for better effect, but exceptions should be added here).
|
// counters for better effect, but exceptions should be added here).
|
||||||
Card target = (Card)params.get("Target");
|
Card target = (Card)params.get("Target");
|
||||||
return !ComputerUtil.isNegativeCounter(CounterEnumType.TIME, target);
|
return !ComputerUtil.isNegativeCounter(CounterType.get(CounterEnumType.TIME), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ public final class ImageKeys {
|
|||||||
public static final String MONARCH_IMAGE = "monarch";
|
public static final String MONARCH_IMAGE = "monarch";
|
||||||
public static final String THE_RING_IMAGE = "the_ring";
|
public static final String THE_RING_IMAGE = "the_ring";
|
||||||
public static final String RADIATION_IMAGE = "radiation";
|
public static final String RADIATION_IMAGE = "radiation";
|
||||||
public static final String SPEED_IMAGE = "speed";
|
|
||||||
public static final String MAX_SPEED_IMAGE = "max_speed";
|
|
||||||
|
|
||||||
public static final String BACKFACE_POSTFIX = "$alt";
|
public static final String BACKFACE_POSTFIX = "$alt";
|
||||||
public static final String SPECFACE_W = "$wspec";
|
public static final String SPECFACE_W = "$wspec";
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import java.util.*;
|
|||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class holding game invariants, such as cards, editions, game formats. All that data, which is not supposed to be changed by player
|
* The class holding game invariants, such as cards, editions, game formats. All that data, which is not supposed to be changed by player
|
||||||
*
|
*
|
||||||
@@ -29,6 +29,8 @@ import java.util.stream.Collectors;
|
|||||||
public class StaticData {
|
public class StaticData {
|
||||||
private final CardStorageReader cardReader;
|
private final CardStorageReader cardReader;
|
||||||
private final CardStorageReader tokenReader;
|
private final CardStorageReader tokenReader;
|
||||||
|
private final CardStorageReader customCardReader;
|
||||||
|
|
||||||
private final String blockDataFolder;
|
private final String blockDataFolder;
|
||||||
private final CardDb commonCards;
|
private final CardDb commonCards;
|
||||||
private final CardDb variantCards;
|
private final CardDb variantCards;
|
||||||
@@ -77,6 +79,7 @@ public class StaticData {
|
|||||||
this.tokenReader = tokenReader;
|
this.tokenReader = tokenReader;
|
||||||
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
||||||
this.blockDataFolder = blockDataFolder;
|
this.blockDataFolder = blockDataFolder;
|
||||||
|
this.customCardReader = customCardReader;
|
||||||
this.allowCustomCardsInDecksConformance = allowCustomCardsInDecksConformance;
|
this.allowCustomCardsInDecksConformance = allowCustomCardsInDecksConformance;
|
||||||
this.enableSmartCardArtSelection = enableSmartCardArtSelection;
|
this.enableSmartCardArtSelection = enableSmartCardArtSelection;
|
||||||
this.loadNonLegalCards = loadNonLegalCards;
|
this.loadNonLegalCards = loadNonLegalCards;
|
||||||
@@ -781,7 +784,6 @@ public class StaticData {
|
|||||||
Queue<String> TOKEN_Q = new ConcurrentLinkedQueue<>();
|
Queue<String> TOKEN_Q = new ConcurrentLinkedQueue<>();
|
||||||
boolean nifHeader = false;
|
boolean nifHeader = false;
|
||||||
boolean cniHeader = false;
|
boolean cniHeader = false;
|
||||||
final Pattern funnyCardCollectorNumberPattern = Pattern.compile("^F\\d+");
|
|
||||||
for (CardEdition e : editions) {
|
for (CardEdition e : editions) {
|
||||||
if (CardEdition.Type.FUNNY.equals(e.getType()))
|
if (CardEdition.Type.FUNNY.equals(e.getType()))
|
||||||
continue;
|
continue;
|
||||||
@@ -789,13 +791,11 @@ public class StaticData {
|
|||||||
Map<String, Pair<Boolean, Integer>> cardCount = new HashMap<>();
|
Map<String, Pair<Boolean, Integer>> cardCount = new HashMap<>();
|
||||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||||
for (CardEdition.EditionEntry c : e.getObtainableCards()) {
|
for (CardEdition.EditionEntry c : e.getObtainableCards()) {
|
||||||
int amount = 1;
|
|
||||||
|
|
||||||
if (cardCount.containsKey(c.name())) {
|
if (cardCount.containsKey(c.name())) {
|
||||||
amount = cardCount.get(c.name()).getRight() + 1;
|
cardCount.put(c.name(), Pair.of(c.collectorNumber() != null && c.collectorNumber().startsWith("F"), cardCount.get(c.name()).getRight() + 1));
|
||||||
|
} else {
|
||||||
|
cardCount.put(c.name(), Pair.of(c.collectorNumber() != null && c.collectorNumber().startsWith("F"), 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
cardCount.put(c.name(), Pair.of(c.collectorNumber() != null && funnyCardCollectorNumberPattern.matcher(c.collectorNumber()).matches(), amount));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop through the cards in this edition, considering art variations...
|
// loop through the cards in this edition, considering art variations...
|
||||||
@@ -878,7 +878,7 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||||
List<String> NIF = new ArrayList<>(NIF_Q).stream().sorted().collect(Collectors.toList());
|
List<String> 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());
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import com.google.common.collect.ListMultimap;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimaps;
|
import com.google.common.collect.Multimaps;
|
||||||
import forge.ImageKeys;
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardEdition.EditionEntry;
|
import forge.card.CardEdition.EditionEntry;
|
||||||
import forge.card.CardEdition.Type;
|
import forge.card.CardEdition.Type;
|
||||||
@@ -45,6 +44,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
public final static char NameSetSeparator = '|';
|
public final static char NameSetSeparator = '|';
|
||||||
public final static String FlagPrefix = "#";
|
public final static String FlagPrefix = "#";
|
||||||
public static final String FlagSeparator = "\t";
|
public static final String FlagSeparator = "\t";
|
||||||
|
private final String exlcudedCardName = "Concentrate";
|
||||||
|
private final String exlcudedCardSet = "DS0";
|
||||||
|
|
||||||
// need this to obtain cardReference by name+set+artindex
|
// need this to obtain cardReference by name+set+artindex
|
||||||
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
|
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
|
||||||
@@ -199,7 +200,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isArtIndex(String s) {
|
private static boolean isArtIndex(String s) {
|
||||||
return StringUtils.isNumeric(s) && s.length() <= 2; // only artIndex between 1-99
|
return StringUtils.isNumeric(s) && s.length() <= 2 ; // only artIndex between 1-99
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSetCode(String s) {
|
private static boolean isSetCode(String s) {
|
||||||
@@ -240,8 +241,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
setCode = info[index];
|
setCode = info[index];
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
if(info.length > index && isArtIndex(info[index].replace(ImageKeys.BACKFACE_POSTFIX, ""))) {
|
if(info.length > index && isArtIndex(info[index])) {
|
||||||
artIndex = Integer.parseInt(info[index].replace(ImageKeys.BACKFACE_POSTFIX, ""));
|
artIndex = Integer.parseInt(info[index]);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
if(info.length > index && isCollectorNumber(info[index])) {
|
if(info.length > index && isCollectorNumber(info[index])) {
|
||||||
@@ -301,7 +302,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
// create faces list from rules
|
// create faces list from rules
|
||||||
for (final CardRules rule : rules.values()) {
|
for (final CardRules rule : rules.values()) {
|
||||||
if (filteredCards.contains(rule.getName()))
|
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
|
||||||
continue;
|
continue;
|
||||||
for (ICardFace face : rule.getAllFaces()) {
|
for (ICardFace face : rule.getAllFaces()) {
|
||||||
addFaceToDbNames(face);
|
addFaceToDbNames(face);
|
||||||
@@ -499,9 +500,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addCard(PaperCard paperCard) {
|
public void addCard(PaperCard paperCard) {
|
||||||
if (filtered.contains(paperCard.getName())) {
|
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
allCardsByName.put(paperCard.getName(), paperCard);
|
allCardsByName.put(paperCard.getName(), paperCard);
|
||||||
|
|
||||||
@@ -522,6 +522,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean excludeCard(String cardName, String cardEdition) {
|
||||||
|
if (filtered.isEmpty())
|
||||||
|
return false;
|
||||||
|
if (filtered.contains(cardName)) {
|
||||||
|
if (exlcudedCardSet.equalsIgnoreCase(cardEdition) && exlcudedCardName.equalsIgnoreCase(cardName))
|
||||||
|
return true;
|
||||||
|
else return !exlcudedCardName.equalsIgnoreCase(cardName);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void reIndex() {
|
private void reIndex() {
|
||||||
uniqueCardsByName.clear();
|
uniqueCardsByName.clear();
|
||||||
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
||||||
|
|||||||
@@ -52,14 +52,6 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public final class CardEdition implements Comparable<CardEdition> {
|
public final class CardEdition implements Comparable<CardEdition> {
|
||||||
|
|
||||||
public DraftOptions getDraftOptions() {
|
|
||||||
return draftOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDraftOptions(DraftOptions draftOptions) {
|
|
||||||
this.draftOptions = draftOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// immutable
|
// immutable
|
||||||
public enum Type {
|
public enum Type {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
@@ -283,22 +275,18 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
// Booster/draft info
|
// Booster/draft info
|
||||||
private List<BoosterSlot> boosterSlots = null;
|
private List<BoosterSlot> boosterSlots = null;
|
||||||
private boolean smallSetOverride = false;
|
private boolean smallSetOverride = false;
|
||||||
private String additionalUnlockSet = "";
|
|
||||||
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
|
||||||
|
|
||||||
// Replace all of these things with booster slots
|
|
||||||
private boolean foilAlwaysInCommonSlot = false;
|
private boolean foilAlwaysInCommonSlot = false;
|
||||||
|
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
||||||
private double foilChanceInBooster = 0;
|
private double foilChanceInBooster = 0;
|
||||||
private double chanceReplaceCommonWith = 0;
|
private double chanceReplaceCommonWith = 0;
|
||||||
private String slotReplaceCommonWith = "Common";
|
private String slotReplaceCommonWith = "Common";
|
||||||
private String additionalSheetForFoils = "";
|
private String additionalSheetForFoils = "";
|
||||||
|
private String additionalUnlockSet = "";
|
||||||
private String boosterMustContain = "";
|
private String boosterMustContain = "";
|
||||||
private String boosterReplaceSlotFromPrintSheet = "";
|
private String boosterReplaceSlotFromPrintSheet = "";
|
||||||
private String sheetReplaceCardFromSheet = "";
|
private String sheetReplaceCardFromSheet = "";
|
||||||
private String sheetReplaceCardFromSheet2 = "";
|
private String sheetReplaceCardFromSheet2 = "";
|
||||||
|
private String doublePickDuringDraft = "";
|
||||||
// Draft options
|
|
||||||
private DraftOptions draftOptions = null;
|
|
||||||
private String[] chaosDraftThemes = new String[0];
|
private String[] chaosDraftThemes = new String[0];
|
||||||
|
|
||||||
private final ListMultimap<String, EditionEntry> cardMap;
|
private final ListMultimap<String, EditionEntry> cardMap;
|
||||||
@@ -385,6 +373,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
|
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
|
||||||
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
||||||
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
||||||
|
public String getDoublePickDuringDraft() { return doublePickDuringDraft; }
|
||||||
public String getBoosterMustContain() { return boosterMustContain; }
|
public String getBoosterMustContain() { return boosterMustContain; }
|
||||||
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
||||||
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
|
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
|
||||||
@@ -563,16 +552,26 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
|
|
||||||
public List<PrintSheet> getPrintSheetsBySection() {
|
public List<PrintSheet> getPrintSheetsBySection() {
|
||||||
final CardDb cardDb = StaticData.instance().getCommonCards();
|
final CardDb cardDb = StaticData.instance().getCommonCards();
|
||||||
|
Map<String, Integer> cardToIndex = new HashMap<>();
|
||||||
|
|
||||||
List<PrintSheet> sheets = Lists.newArrayList();
|
List<PrintSheet> sheets = Lists.newArrayList();
|
||||||
for (Map.Entry<String, java.util.Collection<EditionEntry>> section : cardMap.asMap().entrySet()) {
|
for (String sectionName : cardMap.keySet()) {
|
||||||
if (section.getKey().equals(EditionSectionWithCollectorNumbers.CONJURED.getName())) {
|
if (sectionName.equals(EditionSectionWithCollectorNumbers.CONJURED.getName())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), section.getKey()));
|
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sectionName));
|
||||||
|
|
||||||
for (EditionEntry card : section.getValue()) {
|
List<EditionEntry> cards = cardMap.get(sectionName);
|
||||||
sheet.add(cardDb.getCard(card.name, this.getCode(), card.collectorNumber));
|
for (EditionEntry card : cards) {
|
||||||
|
int index = 1;
|
||||||
|
if (cardToIndex.containsKey(card.name)) {
|
||||||
|
index = cardToIndex.get(card.name) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cardToIndex.put(card.name, index);
|
||||||
|
|
||||||
|
PaperCard pCard = cardDb.getCard(card.name, this.getCode(), index);
|
||||||
|
sheet.add(pCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
sheets.add(sheet);
|
sheets.add(sheet);
|
||||||
@@ -630,7 +629,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
* functional variant name - grouping #9
|
* functional variant name - grouping #9
|
||||||
*/
|
*/
|
||||||
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
|
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
|
||||||
"(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
|
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
|
||||||
);
|
);
|
||||||
|
|
||||||
final Pattern tokenPattern = Pattern.compile(
|
final Pattern tokenPattern = Pattern.compile(
|
||||||
@@ -639,7 +638,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
* name - grouping #3
|
* name - grouping #3
|
||||||
* artist name - grouping #5
|
* artist name - grouping #5
|
||||||
*/
|
*/
|
||||||
"(^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]*)( @(.*))?$"
|
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
|
||||||
);
|
);
|
||||||
|
|
||||||
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
|
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
|
||||||
@@ -660,37 +659,31 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sectionName.endsWith("Types")) {
|
// parse sections of the format "<collector number> <rarity> <name>"
|
||||||
CardType.Helper.parseTypes(sectionName, contents.get(sectionName));
|
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
||||||
} else {
|
for(String line : contents.get(sectionName)) {
|
||||||
// Parse cards
|
Matcher matcher = pattern.matcher(line);
|
||||||
|
|
||||||
// parse sections of the format "<collector number> <rarity> <name>"
|
if (!matcher.matches()) {
|
||||||
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
continue;
|
||||||
for(String line : contents.get(sectionName)) {
|
|
||||||
Matcher matcher = pattern.matcher(line);
|
|
||||||
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String collectorNumber = matcher.group(2);
|
|
||||||
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
|
||||||
String cardName = matcher.group(5);
|
|
||||||
String artistName = matcher.group(7);
|
|
||||||
String functionalVariantName = matcher.group(9);
|
|
||||||
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
|
|
||||||
|
|
||||||
cardMap.put(sectionName, cis);
|
|
||||||
}
|
}
|
||||||
} else if (boosterSlotsToParse.contains(sectionName)) {
|
|
||||||
// parse booster slots of the format "Base=N\n|Replace=<amount> <sheet>"
|
String collectorNumber = matcher.group(2);
|
||||||
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
|
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
||||||
} else {
|
String cardName = matcher.group(5);
|
||||||
// save custom print sheets of the format "<amount> <name>|<setcode>|<art index>"
|
String artistName = matcher.group(7);
|
||||||
// to parse later when printsheets are loaded lazily (and the cardpool is already initialized)
|
String functionalVariantName = matcher.group(9);
|
||||||
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
|
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
|
||||||
|
|
||||||
|
cardMap.put(sectionName, cis);
|
||||||
}
|
}
|
||||||
|
} else if (boosterSlotsToParse.contains(sectionName)) {
|
||||||
|
// parse booster slots of the format "Base=N\n|Replace=<amount> <sheet>"
|
||||||
|
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
|
||||||
|
} else {
|
||||||
|
// save custom print sheets of the format "<amount> <name>|<setcode>|<art index>"
|
||||||
|
// to parse later when printsheets are loaded lazily (and the cardpool is already initialized)
|
||||||
|
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,6 +812,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
res.additionalUnlockSet = metadata.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
|
res.additionalUnlockSet = metadata.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
|
||||||
|
|
||||||
res.smallSetOverride = metadata.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
|
res.smallSetOverride = metadata.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
|
||||||
|
res.doublePickDuringDraft = metadata.get("DoublePick", ""); // "FirstPick" or "Always"
|
||||||
|
|
||||||
res.boosterMustContain = metadata.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
|
res.boosterMustContain = metadata.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
|
||||||
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
|
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
|
||||||
@@ -826,23 +820,6 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
|
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
|
||||||
res.chaosDraftThemes = metadata.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
|
res.chaosDraftThemes = metadata.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
|
||||||
|
|
||||||
// Draft options
|
|
||||||
String doublePick = metadata.get("DoublePick", "Never");
|
|
||||||
int maxPodSize = metadata.getInt("MaxPodSize", 8);
|
|
||||||
int recommendedPodSize = metadata.getInt("RecommendedPodSize", 8);
|
|
||||||
int maxMatchPlayers = metadata.getInt("MaxMatchPlayers", 2);
|
|
||||||
String deckType = metadata.get("DeckType", "Normal");
|
|
||||||
String freeCommander = metadata.get("FreeCommander", "");
|
|
||||||
|
|
||||||
res.draftOptions = new DraftOptions(
|
|
||||||
doublePick,
|
|
||||||
maxPodSize,
|
|
||||||
recommendedPodSize,
|
|
||||||
maxMatchPlayers,
|
|
||||||
deckType,
|
|
||||||
freeCommander
|
|
||||||
);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,7 +850,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
@Override
|
@Override
|
||||||
public void add(CardEdition item) { //Even though we want it to be read only, make an exception for custom content.
|
public void add(CardEdition item) { //Even though we want it to be read only, make an exception for custom content.
|
||||||
if(lock) throw new UnsupportedOperationException("This is a read-only storage");
|
if(lock) throw new UnsupportedOperationException("This is a read-only storage");
|
||||||
else map.put(item.getCode(), item);
|
else map.put(item.getName(), item);
|
||||||
}
|
}
|
||||||
public void append(CardEdition.Collection C){ //Append custom editions
|
public void append(CardEdition.Collection C){ //Append custom editions
|
||||||
if (lock) throw new UnsupportedOperationException("This is a read-only storage");
|
if (lock) throw new UnsupportedOperationException("This is a read-only storage");
|
||||||
@@ -1018,13 +995,16 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
|
|
||||||
public static final Predicate<CardEdition> HAS_BOOSTER_BOX = edition -> edition.getBoosterBoxCount() > 0;
|
public static final Predicate<CardEdition> HAS_BOOSTER_BOX = edition -> edition.getBoosterBoxCount() > 0;
|
||||||
|
|
||||||
@Deprecated //Use CardEdition::hasBasicLands and a nonnull test.
|
|
||||||
public static final Predicate<CardEdition> hasBasicLands = ed -> {
|
public static final Predicate<CardEdition> hasBasicLands = ed -> {
|
||||||
if (ed == null) {
|
if (ed == null) {
|
||||||
// Happens for new sets with "???" code
|
// Happens for new sets with "???" code
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return ed.hasBasicLands();
|
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||||
|
if (null == StaticData.instance().getCommonCards().getCard(landName, ed.getCode(), 0))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1045,7 +1025,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
|
|
||||||
public boolean hasBasicLands() {
|
public boolean hasBasicLands() {
|
||||||
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||||
if (this.getCardInSet(landName).isEmpty())
|
if (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
private boolean addsWildCardColor;
|
private boolean addsWildCardColor;
|
||||||
private int setColorID;
|
private int setColorID;
|
||||||
private boolean custom;
|
private boolean custom;
|
||||||
private boolean unsupported;
|
|
||||||
private String path;
|
private String path;
|
||||||
|
|
||||||
public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
|
public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
|
||||||
@@ -168,7 +167,21 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTransformable() {
|
public boolean isTransformable() {
|
||||||
return CardSplitType.Transform == getSplitType() || CardSplitType.Modal == getSplitType();
|
if (CardSplitType.Transform == getSplitType()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (CardSplitType.Modal != getSplitType()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (ICardFace face : getAllFaces()) {
|
||||||
|
for (String spell : face.getAbilities()) {
|
||||||
|
if (spell.contains("AB$ SetState") && spell.contains("Mode$ Transform")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO check keywords if needed
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICardFace getWSpecialize() {
|
public ICardFace getWSpecialize() {
|
||||||
@@ -207,9 +220,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCustom() { return custom; }
|
public boolean isCustom() { return custom; }
|
||||||
public void setCustom() { custom = true; }
|
public void setCustom() { custom = true; }
|
||||||
|
|
||||||
public boolean isUnsupported() { return unsupported; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardType getType() {
|
public CardType getType() {
|
||||||
@@ -324,12 +335,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
if (hasKeyword("Friends forever") && b.hasKeyword("Friends forever")) {
|
if (hasKeyword("Friends forever") && b.hasKeyword("Friends forever")) {
|
||||||
legal = true; // Stranger Things Secret Lair gimmick partner commander
|
legal = true; // Stranger Things Secret Lair gimmick partner commander
|
||||||
}
|
|
||||||
if (hasKeyword("Partner - Survivors") && b.hasKeyword("Partner - Survivors")) {
|
|
||||||
legal = true; // The Last of Us Secret Lair gimmick partner commander
|
|
||||||
}
|
|
||||||
if (hasKeyword("Partner - Father & Son") && b.hasKeyword("Partner - Father & Son")) {
|
|
||||||
legal = true; // God of War Secret Lair gimmick partner commander
|
|
||||||
}
|
}
|
||||||
if (hasKeyword("Choose a Background") && b.canBeBackground()
|
if (hasKeyword("Choose a Background") && b.canBeBackground()
|
||||||
|| b.hasKeyword("Choose a Background") && canBeBackground()) {
|
|| b.hasKeyword("Choose a Background") && canBeBackground()) {
|
||||||
@@ -348,7 +353,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
|
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
|
||||||
hasKeyword("Friends forever") || hasKeyword("Choose a Background") ||
|
hasKeyword("Friends forever") || hasKeyword("Choose a Background") ||
|
||||||
hasKeyword("Partner - Father & Son") || hasKeyword("Partner - Survivors") ||
|
|
||||||
hasKeyword("Doctor's companion") || isDoctor());
|
hasKeyword("Doctor's companion") || isDoctor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,21 +361,16 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDoctor() {
|
public boolean isDoctor() {
|
||||||
Set<String> subtypes = new HashSet<>();
|
|
||||||
for (String type : mainPart.getType().getSubtypes()) {
|
for (String type : mainPart.getType().getSubtypes()) {
|
||||||
subtypes.add(type);
|
if (!type.equals("Time Lord") && !type.equals("Doctor")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
return subtypes.size() == 2 &&
|
|
||||||
subtypes.contains("Time Lord") &&
|
|
||||||
subtypes.contains("Doctor");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canBeOathbreaker() {
|
public boolean canBeOathbreaker() {
|
||||||
CardType type = mainPart.getType();
|
CardType type = mainPart.getType();
|
||||||
if (mainPart.getOracleText().contains("can be your commander")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return type.isPlaneswalker();
|
return type.isPlaneswalker();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,8 +823,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
faces[0].assignMissingFields();
|
faces[0].assignMissingFields();
|
||||||
final CardRules result = new CardRules(faces, CardSplitType.None, cah);
|
final CardRules result = new CardRules(faces, CardSplitType.None, cah);
|
||||||
|
|
||||||
result.unsupported = true;
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,38 +189,6 @@ public final class CardRulesPredicates {
|
|||||||
return card -> card.getType().hasSupertype(type);
|
return card -> card.getType().hasSupertype(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a Predicate that matches cards that are of the split type.
|
|
||||||
*/
|
|
||||||
public static Predicate<CardRules> isSplitType(final CardSplitType type) {
|
|
||||||
return card -> card.getSplitType().equals(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a Predicate that matches cards that are vanilla.
|
|
||||||
*/
|
|
||||||
public static Predicate<CardRules> isVanilla() {
|
|
||||||
return card -> {
|
|
||||||
if (!(card.getType().isCreature() || card.getType().isLand()) ||
|
|
||||||
card.getSplitType() != CardSplitType.None ||
|
|
||||||
card.hasFunctionalVariants()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ICardFace mainPart = card.getMainPart();
|
|
||||||
|
|
||||||
boolean hasAny =
|
|
||||||
mainPart.getKeywords().iterator().hasNext() ||
|
|
||||||
mainPart.getAbilities().iterator().hasNext() ||
|
|
||||||
mainPart.getStaticAbilities().iterator().hasNext() ||
|
|
||||||
mainPart.getTriggers().iterator().hasNext() ||
|
|
||||||
(mainPart.getDraftActions() != null && mainPart.getDraftActions().iterator().hasNext()) ||
|
|
||||||
mainPart.getReplacements().iterator().hasNext();
|
|
||||||
|
|
||||||
return !hasAny;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for color.
|
* Checks for color.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1066,74 +1066,4 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Helper {
|
|
||||||
public static final void parseTypes(String sectionName, List<String> content) {
|
|
||||||
Set<String> addToSection = null;
|
|
||||||
|
|
||||||
switch (sectionName) {
|
|
||||||
case "BasicTypes":
|
|
||||||
addToSection = CardType.Constant.BASIC_TYPES;
|
|
||||||
break;
|
|
||||||
case "LandTypes":
|
|
||||||
addToSection = CardType.Constant.LAND_TYPES;
|
|
||||||
break;
|
|
||||||
case "CreatureTypes":
|
|
||||||
addToSection = CardType.Constant.CREATURE_TYPES;
|
|
||||||
break;
|
|
||||||
case "SpellTypes":
|
|
||||||
addToSection = CardType.Constant.SPELL_TYPES;
|
|
||||||
break;
|
|
||||||
case "EnchantmentTypes":
|
|
||||||
addToSection = CardType.Constant.ENCHANTMENT_TYPES;
|
|
||||||
break;
|
|
||||||
case "ArtifactTypes":
|
|
||||||
addToSection = CardType.Constant.ARTIFACT_TYPES;
|
|
||||||
break;
|
|
||||||
case "WalkerTypes":
|
|
||||||
addToSection = CardType.Constant.WALKER_TYPES;
|
|
||||||
break;
|
|
||||||
case "DungeonTypes":
|
|
||||||
addToSection = CardType.Constant.DUNGEON_TYPES;
|
|
||||||
break;
|
|
||||||
case "BattleTypes":
|
|
||||||
addToSection = CardType.Constant.BATTLE_TYPES;
|
|
||||||
break;
|
|
||||||
case "PlanarTypes":
|
|
||||||
addToSection = CardType.Constant.PLANAR_TYPES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addToSection == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(String line : content) {
|
|
||||||
if (line.length() == 0) continue;
|
|
||||||
|
|
||||||
if (line.contains(":")) {
|
|
||||||
String[] k = line.split(":");
|
|
||||||
|
|
||||||
if (addToSection.contains(k[0])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToSection.add(k[0]);
|
|
||||||
CardType.Constant.pluralTypes.put(k[0], k[1]);
|
|
||||||
|
|
||||||
if (k[0].contains(" ")) {
|
|
||||||
CardType.Constant.MultiwordTypes.add(k[0]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (addToSection.contains(line)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToSection.add(line);
|
|
||||||
if (line.contains(" ")) {
|
|
||||||
CardType.Constant.MultiwordTypes.add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package forge.card;
|
|||||||
import com.google.common.collect.UnmodifiableIterator;
|
import com.google.common.collect.UnmodifiableIterator;
|
||||||
import forge.card.MagicColor.Color;
|
import forge.card.MagicColor.Color;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
|
import forge.card.mana.ManaCostShard;
|
||||||
import forge.util.BinaryUtil;
|
import forge.util.BinaryUtil;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -40,95 +41,25 @@ import java.util.stream.Stream;
|
|||||||
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
|
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
|
||||||
private static final long serialVersionUID = 794691267379929080L;
|
private static final long serialVersionUID = 794691267379929080L;
|
||||||
|
|
||||||
// needs to be before other static
|
|
||||||
private static final ColorSet[] cache = new ColorSet[MagicColor.ALL_COLORS + 1];
|
|
||||||
static {
|
|
||||||
byte COLORLESS = MagicColor.COLORLESS;
|
|
||||||
byte WHITE = MagicColor.WHITE;
|
|
||||||
byte BLUE = MagicColor.BLUE;
|
|
||||||
byte BLACK = MagicColor.BLACK;
|
|
||||||
byte RED = MagicColor.RED;
|
|
||||||
byte GREEN = MagicColor.GREEN;
|
|
||||||
Color C = Color.COLORLESS;
|
|
||||||
Color W = Color.WHITE;
|
|
||||||
Color U = Color.BLUE;
|
|
||||||
Color B = Color.BLACK;
|
|
||||||
Color R = Color.RED;
|
|
||||||
Color G = Color.GREEN;
|
|
||||||
|
|
||||||
//colorless
|
|
||||||
cache[COLORLESS] = new ColorSet(C);
|
|
||||||
|
|
||||||
//mono-color
|
|
||||||
cache[WHITE] = new ColorSet(W);
|
|
||||||
cache[BLUE] = new ColorSet(U);
|
|
||||||
cache[BLACK] = new ColorSet(B);
|
|
||||||
cache[RED] = new ColorSet(R);
|
|
||||||
cache[GREEN] = new ColorSet(G);
|
|
||||||
|
|
||||||
//two-color
|
|
||||||
cache[WHITE | BLUE] = new ColorSet(W, U);
|
|
||||||
cache[WHITE | BLACK] = new ColorSet(W, B);
|
|
||||||
cache[BLUE | BLACK] = new ColorSet(U, B);
|
|
||||||
cache[BLUE | RED] = new ColorSet(U, R);
|
|
||||||
cache[BLACK | RED] = new ColorSet(B, R);
|
|
||||||
cache[BLACK | GREEN] = new ColorSet(B, G);
|
|
||||||
cache[RED | GREEN] = new ColorSet(R, G);
|
|
||||||
cache[RED | WHITE] = new ColorSet(R, W);
|
|
||||||
cache[GREEN | WHITE] = new ColorSet(G, W);
|
|
||||||
cache[GREEN | BLUE] = new ColorSet(G, U);
|
|
||||||
|
|
||||||
//three-color
|
|
||||||
cache[WHITE | BLUE | BLACK] = new ColorSet(W, U, B);
|
|
||||||
cache[WHITE | BLACK | GREEN] = new ColorSet(W, B, G);
|
|
||||||
cache[BLUE | BLACK | RED] = new ColorSet(U, B, R);
|
|
||||||
cache[BLUE | RED | WHITE] = new ColorSet(U, R, W);
|
|
||||||
cache[BLACK | RED | GREEN] = new ColorSet(B, R, G);
|
|
||||||
cache[BLACK | GREEN | BLUE] = new ColorSet(B, G, U);
|
|
||||||
cache[RED | GREEN | WHITE] = new ColorSet(R, G, W);
|
|
||||||
cache[RED | WHITE | BLACK] = new ColorSet(R, W, B);
|
|
||||||
cache[GREEN | WHITE | BLUE] = new ColorSet(G, W, U);
|
|
||||||
cache[GREEN | BLUE | RED] = new ColorSet(G, U, R);
|
|
||||||
|
|
||||||
//four-color
|
|
||||||
cache[WHITE | BLUE | BLACK | RED] = new ColorSet(W, U, B, R);
|
|
||||||
cache[BLUE | BLACK | RED | GREEN] = new ColorSet(U, B, R, G);
|
|
||||||
cache[BLACK | RED | GREEN | WHITE] = new ColorSet(B, R, G, W);
|
|
||||||
cache[RED | GREEN | WHITE | BLUE] = new ColorSet(R, G, W, U);
|
|
||||||
cache[GREEN | WHITE | BLUE | BLACK] = new ColorSet(G, W, U, B);
|
|
||||||
|
|
||||||
//five-color
|
|
||||||
cache[WHITE | BLUE | BLACK | RED | GREEN] = new ColorSet(W, U, B, R, G);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Collection<Color> orderedShards;
|
|
||||||
private final byte myColor;
|
private final byte myColor;
|
||||||
private final float orderWeight;
|
private final float orderWeight;
|
||||||
private final Set<Color> enumSet;
|
|
||||||
private final String desc;
|
private static final ColorSet[] cache = new ColorSet[32];
|
||||||
|
|
||||||
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
|
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
|
||||||
public static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
private static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
||||||
|
|
||||||
private ColorSet(final Color... ordered) {
|
private ColorSet(final byte mask) {
|
||||||
this.orderedShards = Arrays.asList(ordered);
|
this.myColor = mask;
|
||||||
this.myColor = orderedShards.stream().map(Color::getColorMask).reduce((byte)0, (a, b) -> (byte)(a | b));
|
|
||||||
this.orderWeight = this.getOrderWeight();
|
this.orderWeight = this.getOrderWeight();
|
||||||
this.enumSet = EnumSet.copyOf(orderedShards);
|
|
||||||
this.desc = orderedShards.stream().map(Color::getShortName).collect(Collectors.joining());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ColorSet fromMask(final int mask) {
|
public static ColorSet fromMask(final int mask) {
|
||||||
final int mask32 = mask & MagicColor.ALL_COLORS;
|
final int mask32 = mask & MagicColor.ALL_COLORS;
|
||||||
return cache[mask32];
|
if (cache[mask32] == null) {
|
||||||
}
|
cache[mask32] = new ColorSet((byte) mask32);
|
||||||
|
|
||||||
public static ColorSet fromEnums(final Color... colors) {
|
|
||||||
byte mask = 0;
|
|
||||||
for (Color e : colors) {
|
|
||||||
mask |= e.getColorMask();
|
|
||||||
}
|
}
|
||||||
return fromMask(mask);
|
return cache[mask32];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ColorSet fromNames(final String... colors) {
|
public static ColorSet fromNames(final String... colors) {
|
||||||
@@ -362,7 +293,17 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return desc;
|
final ManaCostShard[] orderedShards = getOrderedShards();
|
||||||
|
return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the null color.
|
||||||
|
*
|
||||||
|
* @return the nullColor
|
||||||
|
*/
|
||||||
|
public static ColorSet getNullColor() {
|
||||||
|
return NO_COLORS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -384,7 +325,16 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Set<Color> toEnumSet() {
|
public Set<Color> toEnumSet() {
|
||||||
return EnumSet.copyOf(enumSet);
|
if (isColorless()) {
|
||||||
|
return EnumSet.of(Color.COLORLESS);
|
||||||
|
}
|
||||||
|
List<Color> list = new ArrayList<>();
|
||||||
|
for (Color c : Color.values()) {
|
||||||
|
if (hasAnyColor(c.getColormask())) {
|
||||||
|
list.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EnumSet.copyOf(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -422,12 +372,72 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<Color> stream() {
|
public Stream<MagicColor.Color> stream() {
|
||||||
return this.toEnumSet().stream();
|
return this.toEnumSet().stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get array of mana cost shards for color set in the proper order
|
//Get array of mana cost shards for color set in the proper order
|
||||||
public Collection<Color> getOrderedColors() {
|
public ManaCostShard[] getOrderedShards() {
|
||||||
return orderedShards;
|
return shardOrderLookup[myColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ManaCostShard[][] shardOrderLookup = new ManaCostShard[MagicColor.ALL_COLORS + 1][];
|
||||||
|
static {
|
||||||
|
byte COLORLESS = MagicColor.COLORLESS;
|
||||||
|
byte WHITE = MagicColor.WHITE;
|
||||||
|
byte BLUE = MagicColor.BLUE;
|
||||||
|
byte BLACK = MagicColor.BLACK;
|
||||||
|
byte RED = MagicColor.RED;
|
||||||
|
byte GREEN = MagicColor.GREEN;
|
||||||
|
ManaCostShard C = ManaCostShard.COLORLESS;
|
||||||
|
ManaCostShard W = ManaCostShard.WHITE;
|
||||||
|
ManaCostShard U = ManaCostShard.BLUE;
|
||||||
|
ManaCostShard B = ManaCostShard.BLACK;
|
||||||
|
ManaCostShard R = ManaCostShard.RED;
|
||||||
|
ManaCostShard G = ManaCostShard.GREEN;
|
||||||
|
|
||||||
|
//colorless
|
||||||
|
shardOrderLookup[COLORLESS] = new ManaCostShard[] { C };
|
||||||
|
|
||||||
|
//mono-color
|
||||||
|
shardOrderLookup[WHITE] = new ManaCostShard[] { W };
|
||||||
|
shardOrderLookup[BLUE] = new ManaCostShard[] { U };
|
||||||
|
shardOrderLookup[BLACK] = new ManaCostShard[] { B };
|
||||||
|
shardOrderLookup[RED] = new ManaCostShard[] { R };
|
||||||
|
shardOrderLookup[GREEN] = new ManaCostShard[] { G };
|
||||||
|
|
||||||
|
//two-color
|
||||||
|
shardOrderLookup[WHITE | BLUE] = new ManaCostShard[] { W, U };
|
||||||
|
shardOrderLookup[WHITE | BLACK] = new ManaCostShard[] { W, B };
|
||||||
|
shardOrderLookup[BLUE | BLACK] = new ManaCostShard[] { U, B };
|
||||||
|
shardOrderLookup[BLUE | RED] = new ManaCostShard[] { U, R };
|
||||||
|
shardOrderLookup[BLACK | RED] = new ManaCostShard[] { B, R };
|
||||||
|
shardOrderLookup[BLACK | GREEN] = new ManaCostShard[] { B, G };
|
||||||
|
shardOrderLookup[RED | GREEN] = new ManaCostShard[] { R, G };
|
||||||
|
shardOrderLookup[RED | WHITE] = new ManaCostShard[] { R, W };
|
||||||
|
shardOrderLookup[GREEN | WHITE] = new ManaCostShard[] { G, W };
|
||||||
|
shardOrderLookup[GREEN | BLUE] = new ManaCostShard[] { G, U };
|
||||||
|
|
||||||
|
//three-color
|
||||||
|
shardOrderLookup[WHITE | BLUE | BLACK] = new ManaCostShard[] { W, U, B };
|
||||||
|
shardOrderLookup[WHITE | BLACK | GREEN] = new ManaCostShard[] { W, B, G };
|
||||||
|
shardOrderLookup[BLUE | BLACK | RED] = new ManaCostShard[] { U, B, R };
|
||||||
|
shardOrderLookup[BLUE | RED | WHITE] = new ManaCostShard[] { U, R, W };
|
||||||
|
shardOrderLookup[BLACK | RED | GREEN] = new ManaCostShard[] { B, R, G };
|
||||||
|
shardOrderLookup[BLACK | GREEN | BLUE] = new ManaCostShard[] { B, G, U };
|
||||||
|
shardOrderLookup[RED | GREEN | WHITE] = new ManaCostShard[] { R, G, W };
|
||||||
|
shardOrderLookup[RED | WHITE | BLACK] = new ManaCostShard[] { R, W, B };
|
||||||
|
shardOrderLookup[GREEN | WHITE | BLUE] = new ManaCostShard[] { G, W, U };
|
||||||
|
shardOrderLookup[GREEN | BLUE | RED] = new ManaCostShard[] { G, U, R };
|
||||||
|
|
||||||
|
//four-color
|
||||||
|
shardOrderLookup[WHITE | BLUE | BLACK | RED] = new ManaCostShard[] { W, U, B, R };
|
||||||
|
shardOrderLookup[BLUE | BLACK | RED | GREEN] = new ManaCostShard[] { U, B, R, G };
|
||||||
|
shardOrderLookup[BLACK | RED | GREEN | WHITE] = new ManaCostShard[] { B, R, G, W };
|
||||||
|
shardOrderLookup[RED | GREEN | WHITE | BLUE] = new ManaCostShard[] { R, G, W, U };
|
||||||
|
shardOrderLookup[GREEN | WHITE | BLUE | BLACK] = new ManaCostShard[] { G, W, U, B };
|
||||||
|
|
||||||
|
//five-color
|
||||||
|
shardOrderLookup[WHITE | BLUE | BLACK | RED | GREEN] = new ManaCostShard[] { W, U, B, R, G };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package forge.card;
|
|
||||||
|
|
||||||
public class DraftOptions {
|
|
||||||
public enum DoublePick {
|
|
||||||
NEVER,
|
|
||||||
FIRST_PICK, // only first pick each pack
|
|
||||||
WHEN_POD_SIZE_IS_4, // only when pod size is 4, so you can pick two cards each time
|
|
||||||
ALWAYS // each time you receive a pack, you can pick two cards
|
|
||||||
};
|
|
||||||
public enum DeckType {
|
|
||||||
Normal, // Standard deck, usually 40 cards
|
|
||||||
Commander // Special deck type for Commander format. Important for selection/construction
|
|
||||||
}
|
|
||||||
|
|
||||||
private DoublePick doublePick = DoublePick.NEVER;
|
|
||||||
private final int maxPodSize; // Usually 8, but could be smaller for cubes. I guess it could be larger too
|
|
||||||
private final int recommendedPodSize; // Usually 8, but is 4 for new double pick
|
|
||||||
private final int maxMatchPlayers; // Usually 2, but 4 for things like Commander or Conspiracy
|
|
||||||
private final DeckType deckType; // Normal or Commander
|
|
||||||
private final String freeCommander;
|
|
||||||
|
|
||||||
public DraftOptions(String doublePickOption, int maxPodSize, int recommendedPodSize, int maxMatchPlayers, String deckType, String freeCommander) {
|
|
||||||
this.maxPodSize = maxPodSize;
|
|
||||||
this.recommendedPodSize = recommendedPodSize;
|
|
||||||
this.maxMatchPlayers = maxMatchPlayers;
|
|
||||||
this.deckType = DeckType.valueOf(deckType);
|
|
||||||
this.freeCommander = freeCommander;
|
|
||||||
if (doublePickOption != null) {
|
|
||||||
switch (doublePickOption.toLowerCase()) {
|
|
||||||
case "firstpick":
|
|
||||||
doublePick = DoublePick.FIRST_PICK;
|
|
||||||
break;
|
|
||||||
case "always":
|
|
||||||
doublePick = DoublePick.ALWAYS;
|
|
||||||
break;
|
|
||||||
case "whenpodsizeis4":
|
|
||||||
doublePick = DoublePick.WHEN_POD_SIZE_IS_4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
public int getMaxPodSize() {
|
|
||||||
return maxPodSize;
|
|
||||||
}
|
|
||||||
public int getRecommendedPodSize() {
|
|
||||||
return recommendedPodSize;
|
|
||||||
}
|
|
||||||
public DoublePick getDoublePick() {
|
|
||||||
return doublePick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DoublePick isDoublePick(int podSize) {
|
|
||||||
if (doublePick == DoublePick.WHEN_POD_SIZE_IS_4) {
|
|
||||||
if (podSize != 4) {
|
|
||||||
return DoublePick.NEVER;
|
|
||||||
}
|
|
||||||
// only when pod size is 4, so you can pick two cards each time
|
|
||||||
return DoublePick.ALWAYS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return doublePick;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getMaxMatchPlayers() {
|
|
||||||
return maxMatchPlayers;
|
|
||||||
}
|
|
||||||
public DeckType getDeckType() {
|
|
||||||
return deckType;
|
|
||||||
}
|
|
||||||
public String getFreeCommander() {
|
|
||||||
return freeCommander;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import forge.deck.DeckRecognizer;
|
||||||
import forge.util.ITranslatable;
|
|
||||||
import forge.util.Localizer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds byte values for each color magic has.
|
* Holds byte values for each color magic has.
|
||||||
@@ -159,24 +157,21 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Color implements ITranslatable {
|
public enum Color {
|
||||||
WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"),
|
WHITE(Constant.WHITE, MagicColor.WHITE, "{W}"),
|
||||||
BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"),
|
BLUE(Constant.BLUE, MagicColor.BLUE, "{U}"),
|
||||||
BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"),
|
BLACK(Constant.BLACK, MagicColor.BLACK, "{B}"),
|
||||||
RED(Constant.RED, MagicColor.RED, "R", "lblRed"),
|
RED(Constant.RED, MagicColor.RED, "{R}"),
|
||||||
GREEN(Constant.GREEN, MagicColor.GREEN, "G", "lblGreen"),
|
GREEN(Constant.GREEN, MagicColor.GREEN, "{G}"),
|
||||||
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "C", "lblColorless");
|
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "{C}");
|
||||||
|
|
||||||
private final String name, shortName, symbol;
|
private final String name, symbol;
|
||||||
private final String label;
|
|
||||||
private final byte colormask;
|
private final byte colormask;
|
||||||
|
|
||||||
Color(String name0, byte colormask0, String shortName, String label) {
|
Color(String name0, byte colormask0, String symbol0) {
|
||||||
name = name0;
|
name = name0;
|
||||||
colormask = colormask0;
|
colormask = colormask0;
|
||||||
this.shortName = shortName;
|
symbol = symbol0;
|
||||||
symbol = "{" + shortName + "}";
|
|
||||||
this.label = label;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color fromByte(final byte color) {
|
public static Color fromByte(final byte color) {
|
||||||
@@ -190,25 +185,25 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
public String getShortName() {
|
|
||||||
return shortName;
|
public String getLocalizedName() {
|
||||||
|
//Should probably move some of this logic back here, or at least to a more general location.
|
||||||
|
return DeckRecognizer.getLocalisedMagicColorName(getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public byte getColormask() {
|
||||||
public String getTranslatedName() {
|
|
||||||
return Localizer.getInstance().getMessage(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getColorMask() {
|
|
||||||
return colormask;
|
return colormask;
|
||||||
}
|
}
|
||||||
public String getSymbol() {
|
public String getSymbol() {
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Write javadoc for this type.
|
* TODO: Write javadoc for this type.
|
||||||
@@ -69,13 +68,6 @@ public class PrintSheet {
|
|||||||
cardsWithWeights.remove(card);
|
cardsWithWeights.remove(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean contains(PaperCard pc) {
|
|
||||||
return cardsWithWeights.contains(pc);
|
|
||||||
}
|
|
||||||
public PaperCard find(Predicate<PaperCard> filter) {
|
|
||||||
return cardsWithWeights.find(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PaperCard fetchRoulette(int start, int roulette, Collection<PaperCard> toSkip) {
|
private PaperCard fetchRoulette(int start, int roulette, Collection<PaperCard> toSkip) {
|
||||||
int sum = start;
|
int sum = start;
|
||||||
boolean isSecondRun = start > 0;
|
boolean isSecondRun = start > 0;
|
||||||
@@ -93,6 +85,15 @@ public class PrintSheet {
|
|||||||
return fetchRoulette(sum + 1, roulette, toSkip); // start over from beginning, in case last cards were to skip
|
return fetchRoulette(sum + 1, roulette, toSkip); // start over from beginning, in case last cards were to skip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PaperCard> all() {
|
||||||
|
List<PaperCard> result = new ArrayList<>();
|
||||||
|
for (Entry<PaperCard, Integer> kv : cardsWithWeights) {
|
||||||
|
for (int i = 0; i < kv.getValue(); i++) {
|
||||||
|
result.add(kv.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
public boolean containsCardNamed(String name,int atLeast) {
|
public boolean containsCardNamed(String name,int atLeast) {
|
||||||
int count=0;
|
int count=0;
|
||||||
for (Entry<PaperCard, Integer> kv : cardsWithWeights) {
|
for (Entry<PaperCard, Integer> kv : cardsWithWeights) {
|
||||||
@@ -143,7 +144,7 @@ public class PrintSheet {
|
|||||||
return cardsWithWeights.isEmpty();
|
return cardsWithWeights.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaperCard> toFlatList() {
|
public Iterable<PaperCard> toFlatList() {
|
||||||
return cardsWithWeights.toFlatList();
|
return cardsWithWeights.toFlatList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import java.util.function.Predicate;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
public class CardPool extends ItemPool<PaperCard> {
|
public class CardPool extends ItemPool<PaperCard> {
|
||||||
private static final long serialVersionUID = -5379091255613968393L;
|
private static final long serialVersionUID = -5379091255613968393L;
|
||||||
|
|
||||||
@@ -77,20 +78,12 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
|
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
|
||||||
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
|
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
|
||||||
CardDb db = entry.getValue();
|
CardDb db = entry.getValue();
|
||||||
|
|
||||||
PaperCard paperCard = db.getCard(cardName, setCode, collectorNumber, flags);
|
PaperCard paperCard = db.getCard(cardName, setCode, collectorNumber, flags);
|
||||||
if (paperCard != null) {
|
if (paperCard != null) {
|
||||||
this.add(paperCard, amount);
|
this.add(paperCard, amount);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get non-Alchemy version if it cannot find it.
|
|
||||||
if (cardName.startsWith("A-")) {
|
|
||||||
System.out.println("Alchemy card not found for '" + cardName + "'. Trying to get its non-Alchemy equivalent.");
|
|
||||||
cardName = cardName.replaceFirst("A-", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Failed to find it. Fall back accordingly?
|
//Failed to find it. Fall back accordingly?
|
||||||
this.add(cardName, setCode, IPaperCard.NO_ART_INDEX, amount, addAny, flags);
|
this.add(cardName, setCode, IPaperCard.NO_ART_INDEX, amount, addAny, flags);
|
||||||
}
|
}
|
||||||
@@ -426,12 +419,6 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CardPool fromSingleCardRequest(String cardRequest) {
|
|
||||||
if(StringUtils.isBlank(cardRequest))
|
|
||||||
return new CardPool();
|
|
||||||
return fromCardList(List.of(cardRequest));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Pair<String, Integer>> processCardList(final Iterable<String> lines) {
|
public static List<Pair<String, Integer>> processCardList(final Iterable<String> lines) {
|
||||||
List<Pair<String, Integer>> cardRequests = new ArrayList<>();
|
List<Pair<String, Integer>> cardRequests = new ArrayList<>();
|
||||||
if (lines == null)
|
if (lines == null)
|
||||||
@@ -481,7 +468,6 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
* @param predicate the Predicate to apply to this CardPool
|
* @param predicate the Predicate to apply to this CardPool
|
||||||
* @return a new CardPool made from this CardPool with only the cards that agree with the provided Predicate
|
* @return a new CardPool made from this CardPool with only the cards that agree with the provided Predicate
|
||||||
*/
|
*/
|
||||||
@Override
|
|
||||||
public CardPool getFilteredPool(Predicate<PaperCard> predicate) {
|
public CardPool getFilteredPool(Predicate<PaperCard> predicate) {
|
||||||
CardPool filteredPool = new CardPool();
|
CardPool filteredPool = new CardPool();
|
||||||
for (PaperCard c : this.items.keySet()) {
|
for (PaperCard c : this.items.keySet()) {
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ import forge.item.PaperCard;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import java.io.ObjectStreamException;
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
@@ -115,20 +113,6 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
return parts.get(DeckSection.Main);
|
return parts.get(DeckSection.Main);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<Deck, List<PaperCard>> getValid() {
|
|
||||||
List<PaperCard> unsupported = new ArrayList<>();
|
|
||||||
for (Entry<DeckSection, CardPool> kv : parts.entrySet()) {
|
|
||||||
CardPool pool = kv.getValue();
|
|
||||||
for (Entry<PaperCard, Integer> pc : pool) {
|
|
||||||
if (pc.getKey().getRules() != null && pc.getKey().getRules().isUnsupported()) {
|
|
||||||
unsupported.add(pc.getKey());
|
|
||||||
pool.remove(pc.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Pair.of(this, unsupported);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PaperCard> getCommanders() {
|
public List<PaperCard> getCommanders() {
|
||||||
List<PaperCard> result = Lists.newArrayList();
|
List<PaperCard> result = Lists.newArrayList();
|
||||||
final CardPool cp = get(DeckSection.Commander);
|
final CardPool cp = get(DeckSection.Commander);
|
||||||
@@ -224,19 +208,14 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
super.cloneFieldsTo(clone);
|
super.cloneFieldsTo(clone);
|
||||||
final Deck result = (Deck) clone;
|
final Deck result = (Deck) clone;
|
||||||
loadDeferredSections();
|
loadDeferredSections();
|
||||||
// parts shouldn't be null
|
for (Entry<DeckSection, CardPool> kv : parts.entrySet()) {
|
||||||
if (parts != null) {
|
CardPool cp = new CardPool();
|
||||||
for (Entry<DeckSection, CardPool> kv : parts.entrySet()) {
|
result.parts.put(kv.getKey(), cp);
|
||||||
CardPool cp = new CardPool();
|
cp.addAll(kv.getValue());
|
||||||
result.parts.put(kv.getKey(), cp);
|
|
||||||
cp.addAll(kv.getValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result.setAiHints(StringUtils.join(aiHints, " | "));
|
result.setAiHints(StringUtils.join(aiHints, " | "));
|
||||||
result.setDraftNotes(draftNotes);
|
result.setDraftNotes(draftNotes);
|
||||||
//noinspection ConstantValue
|
tags.addAll(result.getTags());
|
||||||
if(tags != null) //Can happen deserializing old Decks.
|
|
||||||
result.tags.addAll(this.tags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -542,17 +521,6 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts the number of copies of this exact card print across all deck sections.
|
|
||||||
*/
|
|
||||||
public int count(PaperCard card) {
|
|
||||||
int sum = 0;
|
|
||||||
for (Entry<DeckSection, CardPool> section : this) {
|
|
||||||
sum += section.getValue().count(card);
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAiHints(String aiHintsInfo) {
|
public void setAiHints(String aiHintsInfo) {
|
||||||
if (aiHintsInfo == null || aiHintsInfo.trim().isEmpty()) {
|
if (aiHintsInfo == null || aiHintsInfo.trim().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -646,14 +614,6 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serial
|
|
||||||
private Object readResolve() throws ObjectStreamException {
|
|
||||||
//If we deserialized an old deck that doesn't have tags, fix it here.
|
|
||||||
if(this.tags == null)
|
|
||||||
return new Deck(this, this.getName() == null ? "" : this.getName());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
@@ -703,4 +663,4 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
}
|
}
|
||||||
return totalCount == 0 ? 0 : Math.round(totalCMC / totalCount);
|
return totalCount == 0 ? 0 : Math.round(totalCMC / totalCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,8 +32,11 @@ import forge.util.TextUtil;
|
|||||||
import org.apache.commons.lang3.Range;
|
import org.apache.commons.lang3.Range;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,13 +60,6 @@ public enum DeckFormat {
|
|||||||
//Limited contraption decks have no restrictions.
|
//Limited contraption decks have no restrictions.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExtraSectionMaxCopies(DeckSection section) {
|
|
||||||
if(section == DeckSection.Attractions || section == DeckSection.Contraptions)
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
return super.getExtraSectionMaxCopies(section);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Commander ( Range.is(99), Range.of(0, 10), 1, null,
|
Commander ( Range.is(99), Range.of(0, 10), 1, null,
|
||||||
card -> StaticData.instance().getCommanderPredicate().test(card)
|
card -> StaticData.instance().getCommanderPredicate().test(card)
|
||||||
@@ -112,13 +108,7 @@ public enum DeckFormat {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
PlanarConquest ( Range.of(40, Integer.MAX_VALUE), Range.is(0), 1),
|
PlanarConquest ( Range.of(40, Integer.MAX_VALUE), Range.is(0), 1),
|
||||||
Adventure ( Range.of(40, Integer.MAX_VALUE), Range.of(0, Integer.MAX_VALUE), 4) {
|
Adventure ( Range.of(40, Integer.MAX_VALUE), Range.of(0, 15), 4),
|
||||||
@Override
|
|
||||||
public boolean allowCustomCards() {
|
|
||||||
//If the player has them, may as well allow them.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Vanguard ( Range.of(60, Integer.MAX_VALUE), Range.is(0), 4),
|
Vanguard ( Range.of(60, Integer.MAX_VALUE), Range.is(0), 4),
|
||||||
Planechase ( Range.of(60, Integer.MAX_VALUE), Range.is(0), 4),
|
Planechase ( Range.of(60, Integer.MAX_VALUE), Range.is(0), 4),
|
||||||
Archenemy ( Range.of(60, Integer.MAX_VALUE), Range.is(0), 4),
|
Archenemy ( Range.of(60, Integer.MAX_VALUE), Range.is(0), 4),
|
||||||
@@ -201,57 +191,12 @@ public enum DeckFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the default maximum copies of a card in this format.
|
* @return the maxCardCopies
|
||||||
*/
|
*/
|
||||||
public int getMaxCardCopies() {
|
public int getMaxCardCopies() {
|
||||||
return maxCardCopies;
|
return maxCardCopies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the maximum copies of the specified card allowed in this format. This does not include ban or restricted lists.
|
|
||||||
*/
|
|
||||||
public int getMaxCardCopies(PaperCard card) {
|
|
||||||
if(canHaveSpecificNumberInDeck(card) != null)
|
|
||||||
return canHaveSpecificNumberInDeck(card);
|
|
||||||
else if (canHaveAnyNumberOf(card))
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
else if (card.getRules().isVariant()) {
|
|
||||||
DeckSection section = DeckSection.matchingSection(card);
|
|
||||||
if(section == DeckSection.Planes && card.getRules().getType().isPhenomenon())
|
|
||||||
return 2; //These are two-of.
|
|
||||||
return getExtraSectionMaxCopies(section);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return this.getMaxCardCopies();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getExtraSectionMaxCopies(DeckSection section) {
|
|
||||||
return switch (section) {
|
|
||||||
case Avatar, Commander, Planes, Dungeon, Attractions, Contraptions -> 1;
|
|
||||||
case Schemes -> 2;
|
|
||||||
case Conspiracy -> Integer.MAX_VALUE;
|
|
||||||
default -> maxCardCopies;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the deck sections used by most decks in this format.
|
|
||||||
*/
|
|
||||||
public EnumSet<DeckSection> getPrimaryDeckSections() {
|
|
||||||
if(this == Planechase)
|
|
||||||
return EnumSet.of(DeckSection.Planes);
|
|
||||||
if(this == Archenemy)
|
|
||||||
return EnumSet.of(DeckSection.Schemes);
|
|
||||||
if(this == Vanguard)
|
|
||||||
return EnumSet.of(DeckSection.Avatar);
|
|
||||||
EnumSet<DeckSection> out = EnumSet.of(DeckSection.Main);
|
|
||||||
if(sideRange == null || sideRange.getMaximum() > 0)
|
|
||||||
out.add(DeckSection.Sideboard);
|
|
||||||
if(hasCommander())
|
|
||||||
out.add(DeckSection.Commander);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDeckConformanceProblem(Deck deck) {
|
public String getDeckConformanceProblem(Deck deck) {
|
||||||
if (deck == null) {
|
if (deck == null) {
|
||||||
return "is not selected";
|
return "is not selected";
|
||||||
@@ -408,7 +353,7 @@ public enum DeckFormat {
|
|||||||
// Should group all cards by name, so that different editions of same card are really counted as the same card
|
// Should group all cards by name, so that different editions of same card are really counted as the same card
|
||||||
for (final Entry<String, Integer> cp : Aggregates.groupSumBy(allCards, pc -> StaticData.instance().getCommonCards().getName(pc.getName(), true))) {
|
for (final Entry<String, Integer> cp : Aggregates.groupSumBy(allCards, pc -> StaticData.instance().getCommonCards().getName(pc.getName(), true))) {
|
||||||
IPaperCard simpleCard = StaticData.instance().getCommonCards().getCard(cp.getKey());
|
IPaperCard simpleCard = StaticData.instance().getCommonCards().getCard(cp.getKey());
|
||||||
if (simpleCard != null && simpleCard.getRules().isCustom() && !allowCustomCards())
|
if (simpleCard != null && simpleCard.getRules().isCustom() && !StaticData.instance().allowCustomCardsInDecksConformance())
|
||||||
return TextUtil.concatWithSpace("contains a Custom Card:", cp.getKey(), "\nPlease Enable Custom Cards in Forge Preferences to use this deck.");
|
return TextUtil.concatWithSpace("contains a Custom Card:", cp.getKey(), "\nPlease Enable Custom Cards in Forge Preferences to use this deck.");
|
||||||
// Might cause issues since it ignores "Special" Cards
|
// Might cause issues since it ignores "Special" Cards
|
||||||
if (simpleCard == null) {
|
if (simpleCard == null) {
|
||||||
@@ -539,10 +484,6 @@ public enum DeckFormat {
|
|||||||
// Not needed by default
|
// Not needed by default
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean allowCustomCards() {
|
|
||||||
return StaticData.instance().allowCustomCardsInDecksConformance();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLegalCard(PaperCard pc) {
|
public boolean isLegalCard(PaperCard pc) {
|
||||||
if (cardPoolFilter == null) {
|
if (cardPoolFilter == null) {
|
||||||
if (paperCardPoolFilter == null) {
|
if (paperCardPoolFilter == null) {
|
||||||
@@ -557,13 +498,13 @@ public enum DeckFormat {
|
|||||||
if (cardPoolFilter != null && !cardPoolFilter.test(rules)) {
|
if (cardPoolFilter != null && !cardPoolFilter.test(rules)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this == DeckFormat.Oathbreaker) {
|
if (this.equals(DeckFormat.Oathbreaker)) {
|
||||||
return rules.canBeOathbreaker();
|
return rules.canBeOathbreaker();
|
||||||
}
|
}
|
||||||
if (this == DeckFormat.Brawl) {
|
if (this.equals(DeckFormat.Brawl)) {
|
||||||
return rules.canBeBrawlCommander();
|
return rules.canBeBrawlCommander();
|
||||||
}
|
}
|
||||||
if (this == DeckFormat.TinyLeaders) {
|
if (this.equals(DeckFormat.TinyLeaders)) {
|
||||||
return rules.canBeTinyLeadersCommander();
|
return rules.canBeTinyLeadersCommander();
|
||||||
}
|
}
|
||||||
return rules.canBeCommander();
|
return rules.canBeCommander();
|
||||||
@@ -612,8 +553,6 @@ public enum DeckFormat {
|
|||||||
for (final PaperCard p : commanders) {
|
for (final PaperCard p : commanders) {
|
||||||
cmdCI |= p.getRules().getColorIdentity().getColor();
|
cmdCI |= p.getRules().getColorIdentity().getColor();
|
||||||
}
|
}
|
||||||
if(cmdCI == MagicColor.ALL_COLORS)
|
|
||||||
return x -> true;
|
|
||||||
Predicate<CardRules> predicate = CardRulesPredicates.hasColorIdentity(cmdCI);
|
Predicate<CardRules> predicate = CardRulesPredicates.hasColorIdentity(cmdCI);
|
||||||
if (commanders.size() == 1 && commanders.get(0).getRules().canBePartnerCommander()) {
|
if (commanders.size() == 1 && commanders.get(0).getRules().canBePartnerCommander()) {
|
||||||
// Also show available partners a commander can have a partner.
|
// Also show available partners a commander can have a partner.
|
||||||
|
|||||||
@@ -49,16 +49,6 @@ public class DeckRecognizer {
|
|||||||
LIMITED_CARD,
|
LIMITED_CARD,
|
||||||
CARD_FROM_NOT_ALLOWED_SET,
|
CARD_FROM_NOT_ALLOWED_SET,
|
||||||
CARD_FROM_INVALID_SET,
|
CARD_FROM_INVALID_SET,
|
||||||
/**
|
|
||||||
* Valid card request, but can't be imported because the player does not have enough copies.
|
|
||||||
* Should be replaced with a different printing if possible.
|
|
||||||
*/
|
|
||||||
CARD_NOT_IN_INVENTORY,
|
|
||||||
/**
|
|
||||||
* Valid card request for a card that isn't in the player's inventory, but new copies can be acquired freely.
|
|
||||||
* Usually used for basic lands. Should be supplied to the import controller by the editor.
|
|
||||||
*/
|
|
||||||
FREE_CARD_NOT_IN_INVENTORY,
|
|
||||||
// Warning messages
|
// Warning messages
|
||||||
WARNING_MESSAGE,
|
WARNING_MESSAGE,
|
||||||
UNKNOWN_CARD,
|
UNKNOWN_CARD,
|
||||||
@@ -73,14 +63,10 @@ public class DeckRecognizer {
|
|||||||
CARD_TYPE,
|
CARD_TYPE,
|
||||||
CARD_RARITY,
|
CARD_RARITY,
|
||||||
CARD_CMC,
|
CARD_CMC,
|
||||||
MANA_COLOUR;
|
MANA_COLOUR
|
||||||
|
|
||||||
public static final EnumSet<TokenType> CARD_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET, CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY);
|
|
||||||
public static final EnumSet<TokenType> IN_DECK_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, DECK_NAME, FREE_CARD_NOT_IN_INVENTORY);
|
|
||||||
public static final EnumSet<TokenType> CARD_PLACEHOLDER_TOKEN_TYPES = EnumSet.of(CARD_TYPE, CARD_RARITY, CARD_CMC, MANA_COLOUR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum LimitedCardType {
|
public enum LimitedCardType{
|
||||||
BANNED,
|
BANNED,
|
||||||
RESTRICTED,
|
RESTRICTED,
|
||||||
}
|
}
|
||||||
@@ -122,10 +108,6 @@ public class DeckRecognizer {
|
|||||||
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
|
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Token NotInInventoryFree(final PaperCard card, final int count, final DeckSection section) {
|
|
||||||
return new Token(TokenType.FREE_CARD_NOT_IN_INVENTORY, count, card, section, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING MESSAGES
|
// WARNING MESSAGES
|
||||||
// ================
|
// ================
|
||||||
public static Token UnknownCard(final String cardName, final String setCode, final int count) {
|
public static Token UnknownCard(final String cardName, final String setCode, final int count) {
|
||||||
@@ -144,10 +126,6 @@ public class DeckRecognizer {
|
|||||||
return new Token(TokenType.WARNING_MESSAGE, msg);
|
return new Token(TokenType.WARNING_MESSAGE, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Token NotInInventory(final PaperCard card, final int count, final DeckSection section) {
|
|
||||||
return new Token(TokenType.CARD_NOT_IN_INVENTORY, count, card, section, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================================
|
/* =================================
|
||||||
* DECK SECTIONS
|
* DECK SECTIONS
|
||||||
* ================================= */
|
* ================================= */
|
||||||
@@ -261,11 +239,14 @@ public class DeckRecognizer {
|
|||||||
/**
|
/**
|
||||||
* Filters all token types that have a PaperCard instance set (not null)
|
* Filters all token types that have a PaperCard instance set (not null)
|
||||||
* @return true for tokens of type:
|
* @return true for tokens of type:
|
||||||
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY.
|
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET.
|
||||||
* False otherwise.
|
* False otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isCardToken() {
|
public boolean isCardToken() {
|
||||||
return TokenType.CARD_TOKEN_TYPES.contains(this.type);
|
return (this.type == TokenType.LEGAL_CARD ||
|
||||||
|
this.type == TokenType.LIMITED_CARD ||
|
||||||
|
this.type == TokenType.CARD_FROM_NOT_ALLOWED_SET ||
|
||||||
|
this.type == TokenType.CARD_FROM_INVALID_SET);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -274,7 +255,9 @@ public class DeckRecognizer {
|
|||||||
* LEGAL_CARD, LIMITED_CARD, DECK_NAME; false otherwise.
|
* LEGAL_CARD, LIMITED_CARD, DECK_NAME; false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isTokenForDeck() {
|
public boolean isTokenForDeck() {
|
||||||
return TokenType.IN_DECK_TOKEN_TYPES.contains(this.type);
|
return (this.type == TokenType.LEGAL_CARD ||
|
||||||
|
this.type == TokenType.LIMITED_CARD ||
|
||||||
|
this.type == TokenType.DECK_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -283,7 +266,7 @@ public class DeckRecognizer {
|
|||||||
* False otherwise.
|
* False otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isCardTokenForDeck() {
|
public boolean isCardTokenForDeck() {
|
||||||
return isCardToken() && isTokenForDeck();
|
return (this.type == TokenType.LEGAL_CARD || this.type == TokenType.LIMITED_CARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -293,7 +276,10 @@ public class DeckRecognizer {
|
|||||||
* CARD_RARITY, CARD_CMC, CARD_TYPE, MANA_COLOUR
|
* CARD_RARITY, CARD_CMC, CARD_TYPE, MANA_COLOUR
|
||||||
*/
|
*/
|
||||||
public boolean isCardPlaceholder(){
|
public boolean isCardPlaceholder(){
|
||||||
return TokenType.CARD_PLACEHOLDER_TOKEN_TYPES.contains(this.type);
|
return (this.type == TokenType.CARD_RARITY ||
|
||||||
|
this.type == TokenType.CARD_CMC ||
|
||||||
|
this.type == TokenType.MANA_COLOUR ||
|
||||||
|
this.type == TokenType.CARD_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Determines if current token is a Deck Section token
|
/** Determines if current token is a Deck Section token
|
||||||
@@ -550,7 +536,7 @@ public class DeckRecognizer {
|
|||||||
PaperCard tokenCard = token.getCard();
|
PaperCard tokenCard = token.getCard();
|
||||||
|
|
||||||
if (isAllowed(tokenSection)) {
|
if (isAllowed(tokenSection)) {
|
||||||
if (tokenSection != referenceDeckSectionInParsing) {
|
if (!tokenSection.equals(referenceDeckSectionInParsing)) {
|
||||||
Token sectionToken = Token.DeckSection(tokenSection.name(), this.allowedDeckSections);
|
Token sectionToken = Token.DeckSection(tokenSection.name(), this.allowedDeckSections);
|
||||||
// just check that last token is stack is a card placeholder.
|
// just check that last token is stack is a card placeholder.
|
||||||
// In that case, add the new section token before the placeholder
|
// In that case, add the new section token before the placeholder
|
||||||
@@ -589,7 +575,7 @@ public class DeckRecognizer {
|
|||||||
refLine = purgeAllLinks(refLine);
|
refLine = purgeAllLinks(refLine);
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
if (refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
if (StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
||||||
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
|
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
|
||||||
else
|
else
|
||||||
line = refLine.trim(); // Remove any trailing formatting
|
line = refLine.trim(); // Remove any trailing formatting
|
||||||
@@ -598,7 +584,7 @@ public class DeckRecognizer {
|
|||||||
// Final fantasy cards like Summon: Choco/Mog should be ommited to be recognized. TODO: fix maybe for future cards
|
// Final fantasy cards like Summon: Choco/Mog should be ommited to be recognized. TODO: fix maybe for future cards
|
||||||
if (!line.contains("Summon:"))
|
if (!line.contains("Summon:"))
|
||||||
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
|
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
|
||||||
if (line.startsWith(ASTERISK)) // Markdown lists (tappedout md export)
|
if (StringUtils.startsWith(line, ASTERISK)) // markdown lists (tappedout md export)
|
||||||
line = line.substring(2);
|
line = line.substring(2);
|
||||||
|
|
||||||
// == Patches to Corner Cases
|
// == Patches to Corner Cases
|
||||||
@@ -614,8 +600,8 @@ public class DeckRecognizer {
|
|||||||
Token result = recogniseCardToken(line, referenceSection);
|
Token result = recogniseCardToken(line, referenceSection);
|
||||||
if (result == null)
|
if (result == null)
|
||||||
result = recogniseNonCardToken(line);
|
result = recogniseNonCardToken(line);
|
||||||
return result != null ? result : refLine.startsWith(DOUBLE_SLASH) ||
|
return result != null ? result : StringUtils.startsWith(refLine, DOUBLE_SLASH) ||
|
||||||
refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
||||||
new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine);
|
new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,7 +613,7 @@ public class DeckRecognizer {
|
|||||||
while (m.find()) {
|
while (m.find()) {
|
||||||
line = line.replaceAll(m.group(), "").trim();
|
line = line.replaceAll(m.group(), "").trim();
|
||||||
}
|
}
|
||||||
if (line.endsWith("()"))
|
if (StringUtils.endsWith(line, "()"))
|
||||||
return line.substring(0, line.length()-2);
|
return line.substring(0, line.length()-2);
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -755,12 +741,21 @@ public class DeckRecognizer {
|
|||||||
// This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand
|
// This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand
|
||||||
private DeckSection getTokenSection(String deckSec, DeckSection currentDeckSection, PaperCard card){
|
private DeckSection getTokenSection(String deckSec, DeckSection currentDeckSection, PaperCard card){
|
||||||
if (deckSec != null) {
|
if (deckSec != null) {
|
||||||
DeckSection cardSection = switch (deckSec.toUpperCase().trim()) {
|
DeckSection cardSection;
|
||||||
case "MB" -> DeckSection.Main;
|
switch (deckSec.toUpperCase().trim()) {
|
||||||
case "SB" -> DeckSection.Sideboard;
|
case "MB":
|
||||||
case "CM" -> DeckSection.Commander;
|
cardSection = DeckSection.Main;
|
||||||
default -> DeckSection.matchingSection(card);
|
break;
|
||||||
};
|
case "SB":
|
||||||
|
cardSection = DeckSection.Sideboard;
|
||||||
|
break;
|
||||||
|
case "CM":
|
||||||
|
cardSection = DeckSection.Commander;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cardSection = DeckSection.matchingSection(card);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (cardSection.validate(card))
|
if (cardSection.validate(card))
|
||||||
return cardSection;
|
return cardSection;
|
||||||
}
|
}
|
||||||
@@ -994,7 +989,7 @@ public class DeckRecognizer {
|
|||||||
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
||||||
if (magicColor == null) // Multicolour
|
if (magicColor == null) // Multicolour
|
||||||
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
||||||
return String.format("%s %s", magicColor.getTranslatedName(), magicColor.getSymbol());
|
return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
||||||
@@ -1013,30 +1008,60 @@ public class DeckRecognizer {
|
|||||||
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
||||||
|| magicColor1 == MagicColor.Color.COLORLESS)
|
|| magicColor1 == MagicColor.Color.COLORLESS)
|
||||||
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
||||||
String localisedName1 = magicColor1.getTranslatedName();
|
String localisedName1 = magicColor1.getLocalizedName();
|
||||||
String localisedName2 = magicColor2.getTranslatedName();
|
String localisedName2 = magicColor2.getLocalizedName();
|
||||||
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColorMask() | magicColor2.getColorMask());
|
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
|
||||||
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MagicColor.Color getMagicColor(String colorName){
|
private static MagicColor.Color getMagicColor(String colorName){
|
||||||
if (colorName.toLowerCase().startsWith("multi") || colorName.equalsIgnoreCase("m"))
|
if (colorName.toLowerCase().startsWith("multi") || colorName.equalsIgnoreCase("m"))
|
||||||
return null; // will be handled separately
|
return null; // will be handled separately
|
||||||
return MagicColor.Color.fromByte(MagicColor.fromName(colorName.toLowerCase()));
|
|
||||||
|
byte color = MagicColor.fromName(colorName.toLowerCase());
|
||||||
|
switch (color) {
|
||||||
|
case MagicColor.WHITE:
|
||||||
|
return MagicColor.Color.WHITE;
|
||||||
|
case MagicColor.BLUE:
|
||||||
|
return MagicColor.Color.BLUE;
|
||||||
|
case MagicColor.BLACK:
|
||||||
|
return MagicColor.Color.BLACK;
|
||||||
|
case MagicColor.RED:
|
||||||
|
return MagicColor.Color.RED;
|
||||||
|
case MagicColor.GREEN:
|
||||||
|
return MagicColor.Color.GREEN;
|
||||||
|
default:
|
||||||
|
return MagicColor.Color.COLORLESS;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getLocalisedMagicColorName(String colorName){
|
public static String getLocalisedMagicColorName(String colorName){
|
||||||
Localizer localizer = Localizer.getInstance();
|
Localizer localizer = Localizer.getInstance();
|
||||||
return switch (colorName.toLowerCase()) {
|
switch(colorName.toLowerCase()){
|
||||||
case MagicColor.Constant.WHITE -> localizer.getMessage("lblWhite");
|
case MagicColor.Constant.WHITE:
|
||||||
case MagicColor.Constant.BLUE -> localizer.getMessage("lblBlue");
|
return localizer.getMessage("lblWhite");
|
||||||
case MagicColor.Constant.BLACK -> localizer.getMessage("lblBlack");
|
|
||||||
case MagicColor.Constant.RED -> localizer.getMessage("lblRed");
|
case MagicColor.Constant.BLUE:
|
||||||
case MagicColor.Constant.GREEN -> localizer.getMessage("lblGreen");
|
return localizer.getMessage("lblBlue");
|
||||||
case MagicColor.Constant.COLORLESS -> localizer.getMessage("lblColorless");
|
|
||||||
case "multicolour", "multicolor" -> localizer.getMessage("lblMulticolor");
|
case MagicColor.Constant.BLACK:
|
||||||
default -> "";
|
return localizer.getMessage("lblBlack");
|
||||||
};
|
|
||||||
|
case MagicColor.Constant.RED:
|
||||||
|
return localizer.getMessage("lblRed");
|
||||||
|
|
||||||
|
case MagicColor.Constant.GREEN:
|
||||||
|
return localizer.getMessage("lblGreen");
|
||||||
|
|
||||||
|
case MagicColor.Constant.COLORLESS:
|
||||||
|
return localizer.getMessage("lblColorless");
|
||||||
|
case "multicolour":
|
||||||
|
case "multicolor":
|
||||||
|
return localizer.getMessage("lblMulticolor");
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1055,6 +1080,37 @@ public class DeckRecognizer {
|
|||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Pair<String, String> getManaNameAndSymbol(String matchedMana) {
|
||||||
|
if (matchedMana == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Localizer localizer = Localizer.getInstance();
|
||||||
|
switch (matchedMana.toLowerCase()) {
|
||||||
|
case MagicColor.Constant.WHITE:
|
||||||
|
case "w":
|
||||||
|
return Pair.of(localizer.getMessage("lblWhite"), MagicColor.Color.WHITE.getSymbol());
|
||||||
|
case MagicColor.Constant.BLUE:
|
||||||
|
case "u":
|
||||||
|
return Pair.of(localizer.getMessage("lblBlue"), MagicColor.Color.BLUE.getSymbol());
|
||||||
|
case MagicColor.Constant.BLACK:
|
||||||
|
case "b":
|
||||||
|
return Pair.of(localizer.getMessage("lblBlack"), MagicColor.Color.BLACK.getSymbol());
|
||||||
|
case MagicColor.Constant.RED:
|
||||||
|
case "r":
|
||||||
|
return Pair.of(localizer.getMessage("lblRed"), MagicColor.Color.RED.getSymbol());
|
||||||
|
case MagicColor.Constant.GREEN:
|
||||||
|
case "g":
|
||||||
|
return Pair.of(localizer.getMessage("lblGreen"), MagicColor.Color.GREEN.getSymbol());
|
||||||
|
case MagicColor.Constant.COLORLESS:
|
||||||
|
case "c":
|
||||||
|
return Pair.of(localizer.getMessage("lblColorless"), MagicColor.Color.COLORLESS.getSymbol());
|
||||||
|
default: // Multicolour
|
||||||
|
return Pair.of(localizer.getMessage("lblMulticolor"), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isDeckName(final String lineAsIs) {
|
public static boolean isDeckName(final String lineAsIs) {
|
||||||
if (lineAsIs == null)
|
if (lineAsIs == null)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -52,4 +52,9 @@ public interface IPaperCard extends InventoryItem, Serializable {
|
|||||||
default String getUntranslatedType() {
|
default String getUntranslatedType() {
|
||||||
return getRules().getType().toString();
|
return getRules().getType().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getUntranslatedOracle() {
|
||||||
|
return getRules().getOracleText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
|
|
||||||
// These fields are kinda PK for PrintedCard
|
// These fields are kinda PK for PrintedCard
|
||||||
private final String name;
|
private final String name;
|
||||||
private String edition;
|
private final String edition;
|
||||||
/* [NEW] Attribute to store reference to CollectorNumber of each PaperCard.
|
/* [NEW] Attribute to store reference to CollectorNumber of each PaperCard.
|
||||||
By default the attribute is marked as "unset" so that it could be retrieved and set.
|
By default the attribute is marked as "unset" so that it could be retrieved and set.
|
||||||
(see getCollectorNumber())
|
(see getCollectorNumber())
|
||||||
@@ -154,31 +154,6 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
return this.noSellVersion;
|
return this.noSellVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaperCard getMeldBaseCard() {
|
|
||||||
if (getRules().getSplitType() != CardSplitType.Meld) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the base part of the meld duo
|
|
||||||
if (getRules().getOtherPart() == null) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
String meldWith = getRules().getMeldWith();
|
|
||||||
if (meldWith == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PrintSheet> sheets = StaticData.instance().getCardEdition(this.edition).getPrintSheetsBySection();
|
|
||||||
for (PrintSheet sheet : sheets) {
|
|
||||||
if (sheet.contains(this)) {
|
|
||||||
return sheet.find(PaperCardPredicates.name(meldWith));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaperCard copyWithoutFlags() {
|
public PaperCard copyWithoutFlags() {
|
||||||
if(this.flaglessVersion == null) {
|
if(this.flaglessVersion == null) {
|
||||||
if(this.flags == PaperCardFlags.IDENTITY_FLAGS)
|
if(this.flags == PaperCardFlags.IDENTITY_FLAGS)
|
||||||
@@ -250,7 +225,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
||||||
this.foil = foil;
|
this.foil = foil;
|
||||||
this.rarity = rarity;
|
this.rarity = rarity;
|
||||||
this.artist = artist;
|
this.artist = TextUtil.normalizeText(artist);
|
||||||
this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER;
|
this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER;
|
||||||
// If the user changes the language this will make cards sort by the old language until they restart the game.
|
// If the user changes the language this will make cards sort by the old language until they restart the game.
|
||||||
// This is a good tradeoff
|
// This is a good tradeoff
|
||||||
@@ -375,8 +350,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
|
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
|
||||||
pc = readObjectAlternate(name, edition);
|
pc = readObjectAlternate(name, edition);
|
||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
pc = StaticData.instance().getCommonCards().createUnsupportedCard(name);
|
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
||||||
//throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
|
||||||
}
|
}
|
||||||
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
|
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
|
||||||
}
|
}
|
||||||
@@ -593,7 +567,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
|
|
||||||
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
||||||
if(markedColors == null)
|
if(markedColors == null)
|
||||||
markedColors = ColorSet.NO_COLORS;
|
markedColors = ColorSet.getNullColor();
|
||||||
return new PaperCardFlags(this, markedColors, null);
|
return new PaperCardFlags(this, markedColors, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,13 +50,6 @@ public abstract class PaperCardPredicates {
|
|||||||
return new PredicateNames(what);
|
return new PredicateNames(what);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters on a card foil status
|
|
||||||
*/
|
|
||||||
public static Predicate<PaperCard> isFoil(final boolean isFoil) {
|
|
||||||
return new PredicateFoil(isFoil);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class PredicatePrintedWithRarity implements Predicate<PaperCard> {
|
private static final class PredicatePrintedWithRarity implements Predicate<PaperCard> {
|
||||||
private final CardRarity matchingRarity;
|
private final CardRarity matchingRarity;
|
||||||
|
|
||||||
@@ -100,17 +93,6 @@ public abstract class PaperCardPredicates {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class PredicateFoil implements Predicate<PaperCard> {
|
|
||||||
private final boolean operand;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean test(final PaperCard card) { return card.isFoil() == operand; }
|
|
||||||
|
|
||||||
private PredicateFoil(final boolean isFoil) {
|
|
||||||
this.operand = isFoil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class PredicateRarity implements Predicate<PaperCard> {
|
private static final class PredicateRarity implements Predicate<PaperCard> {
|
||||||
private final CardRarity operand;
|
private final CardRarity operand;
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
return false;
|
return false;
|
||||||
CardSplitType cst = this.cardRules.getSplitType();
|
CardSplitType cst = this.cardRules.getSplitType();
|
||||||
//expand this on future for other tokens that has other backsides besides transform..
|
//expand this on future for other tokens that has other backsides besides transform..
|
||||||
return cst == CardSplitType.Transform || cst == CardSplitType.Modal;
|
return cst == CardSplitType.Transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ public class SealedTemplate {
|
|||||||
Pair.of(BoosterSlots.RARE_MYTHIC, 1), Pair.of(BoosterSlots.BASIC_LAND, 1)
|
Pair.of(BoosterSlots.RARE_MYTHIC, 1), Pair.of(BoosterSlots.BASIC_LAND, 1)
|
||||||
));
|
));
|
||||||
|
|
||||||
// This is a generic cube booster. 15 cards, no rarity slots.
|
|
||||||
public final static SealedTemplate genericNoSlotBooster = new SealedTemplate(null, Lists.newArrayList(
|
|
||||||
Pair.of(BoosterSlots.ANY, 15)
|
|
||||||
));
|
|
||||||
|
|
||||||
protected final List<Pair<String, Integer>> slots;
|
protected final List<Pair<String, Integer>> slots;
|
||||||
|
|
||||||
protected final String name;
|
protected final String name;
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ public class BoosterGenerator {
|
|||||||
|
|
||||||
if (sheetKey.startsWith("wholeSheet")) {
|
if (sheetKey.startsWith("wholeSheet")) {
|
||||||
PrintSheet ps = getPrintSheet(sheetKey);
|
PrintSheet ps = getPrintSheet(sheetKey);
|
||||||
result.addAll(ps.toFlatList());
|
result.addAll(ps.all());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ public class BoosterGenerator {
|
|||||||
PrintSheet replaceThis = tryGetStaticSheet(split[0]);
|
PrintSheet replaceThis = tryGetStaticSheet(split[0]);
|
||||||
List<PaperCard> candidates = Lists.newArrayList();
|
List<PaperCard> candidates = Lists.newArrayList();
|
||||||
for (PaperCard p : result) {
|
for (PaperCard p : result) {
|
||||||
if (replaceThis.contains(p)) {
|
if (replaceThis.all().contains(p)) {
|
||||||
candidates.add(candidates.size(), p);
|
candidates.add(candidates.size(), p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,7 +398,7 @@ public class BoosterGenerator {
|
|||||||
PrintSheet replaceThis = tryGetStaticSheet(split[0]);
|
PrintSheet replaceThis = tryGetStaticSheet(split[0]);
|
||||||
List<PaperCard> candidates = Lists.newArrayList();
|
List<PaperCard> candidates = Lists.newArrayList();
|
||||||
for (PaperCard p : result) {
|
for (PaperCard p : result) {
|
||||||
if (replaceThis.contains(p)) {
|
if (replaceThis.all().contains(p)) {
|
||||||
candidates.add(candidates.size(), p);
|
candidates.add(candidates.size(), p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -633,10 +633,7 @@ public class BoosterGenerator {
|
|||||||
System.out.println("Parsing from main code: " + mainCode);
|
System.out.println("Parsing from main code: " + mainCode);
|
||||||
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
||||||
System.out.println("Attempting to lookup: " + sheetName);
|
System.out.println("Attempting to lookup: " + sheetName);
|
||||||
PrintSheet fromSheet = tryGetStaticSheet(sheetName);
|
src = tryGetStaticSheet(sheetName).toFlatList();
|
||||||
if (fromSheet == null)
|
|
||||||
throw new RuntimeException("PrintSheet Error: " + ps.getName() + " didn't find " + sheetName + " from " + mainCode);
|
|
||||||
src = fromSheet.toFlatList();
|
|
||||||
setPred = x -> true;
|
setPred = x -> true;
|
||||||
|
|
||||||
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet
|
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ public interface ITranslatable extends IHasName {
|
|||||||
default String getUntranslatedName() {
|
default String getUntranslatedName() {
|
||||||
return getName();
|
return getName();
|
||||||
}
|
}
|
||||||
default String getTranslatedName() {
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
default String getUntranslatedType() {
|
default String getUntranslatedType() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default String getUntranslatedOracle() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,14 +199,19 @@ public class ImageUtil {
|
|||||||
return getImageRelativePath(cp, face, true, true);
|
return getImageRelativePath(cp, face, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop){
|
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop){
|
||||||
|
return getScryfallDownloadUrl(cp, face, setCode, langCode, useArtCrop, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop, boolean hyphenateAlchemy){
|
||||||
String editionCode;
|
String editionCode;
|
||||||
if (setCode != null && !setCode.isEmpty())
|
if (setCode != null && !setCode.isEmpty())
|
||||||
editionCode = setCode;
|
editionCode = setCode;
|
||||||
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";
|
||||||
@@ -217,42 +222,29 @@ public class ImageUtil {
|
|||||||
} else if (cardCollectorNumber.startsWith("OPC2")) {
|
} else if (cardCollectorNumber.startsWith("OPC2")) {
|
||||||
editionCode = "opc2";
|
editionCode = "opc2";
|
||||||
cardCollectorNumber = cardCollectorNumber.substring("OPC2".length());
|
cardCollectorNumber = cardCollectorNumber.substring("OPC2".length());
|
||||||
|
} else if (hyphenateAlchemy) {
|
||||||
|
if (!cardCollectorNumber.startsWith("A")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
cardCollectorNumber = cardCollectorNumber.replace("A", "A-");
|
||||||
}
|
}
|
||||||
String versionParam = useArtCrop ? "art_crop" : "normal";
|
String versionParam = useArtCrop ? "art_crop" : "normal";
|
||||||
String faceParam = "";
|
String faceParam = "";
|
||||||
|
if (cp.getRules().getOtherPart() != null) {
|
||||||
|
faceParam = (face.equals("back") ? "&face=back" : "&face=front");
|
||||||
|
} else if (cp.getRules().getSplitType() == CardSplitType.Meld
|
||||||
|
&& !cardCollectorNumber.endsWith("a")
|
||||||
|
&& !cardCollectorNumber.endsWith("b")) {
|
||||||
|
|
||||||
if (cp.getRules().getSplitType() == CardSplitType.Meld) {
|
// Only the bottom half of a meld card shares a collector number.
|
||||||
if (face.equals("back")) {
|
// Hanweir Garrison EMN already has a appended.
|
||||||
PaperCard meldBasePc = cp.getMeldBaseCard();
|
// Exception: The front facing card doesn't use a in FIN
|
||||||
cardCollectorNumber = meldBasePc.getCollectorNumber();
|
if (face.equals("back")) {
|
||||||
String collectorNumberSuffix = "";
|
cardCollectorNumber += "b";
|
||||||
|
} else if (!editionCode.equals("fin")) {
|
||||||
if (cardCollectorNumber.endsWith("a")) {
|
cardCollectorNumber += "a";
|
||||||
cardCollectorNumber = cardCollectorNumber.substring(0, cardCollectorNumber.length() - 1);
|
|
||||||
} else if (cardCollectorNumber.endsWith("as")) {
|
|
||||||
cardCollectorNumber = cardCollectorNumber.substring(0, cardCollectorNumber.length() - 2);
|
|
||||||
collectorNumberSuffix = "s";
|
|
||||||
} else if (cardCollectorNumber.endsWith("ap")) {
|
|
||||||
cardCollectorNumber = cardCollectorNumber.substring(0, cardCollectorNumber.length() - 2);
|
|
||||||
collectorNumberSuffix = "p";
|
|
||||||
} else if (cp.getCollectorNumber().endsWith("a")) {
|
|
||||||
// SIR
|
|
||||||
cardCollectorNumber = cp.getCollectorNumber().substring(0, cp.getCollectorNumber().length() - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cardCollectorNumber += "b" + collectorNumberSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
faceParam = "&face=front";
|
|
||||||
} else if (cp.getRules().getOtherPart() != null) {
|
|
||||||
faceParam = (face.equals("back") && cp.getRules().getSplitType() != CardSplitType.Flip
|
|
||||||
? "&face=back"
|
|
||||||
: "&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),
|
||||||
@@ -264,10 +256,6 @@ public class ImageUtil {
|
|||||||
if (!faceParam.isEmpty()) {
|
if (!faceParam.isEmpty()) {
|
||||||
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
|
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
|
||||||
}
|
}
|
||||||
if (collectorNumber.endsWith("☇")) {
|
|
||||||
faceParam = "&face=back";
|
|
||||||
collectorNumber = collectorNumber.substring(0, collectorNumber.length() - 1);
|
|
||||||
}
|
|
||||||
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, encodeUtf8(collectorNumber),
|
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, encodeUtf8(collectorNumber),
|
||||||
langCode, versionParam, faceParam);
|
langCode, versionParam, faceParam);
|
||||||
}
|
}
|
||||||
@@ -288,7 +276,8 @@ public class ImageUtil {
|
|||||||
char c;
|
char c;
|
||||||
for (int i = 0; i < in.length(); i++) {
|
for (int i = 0; i < in.length(); i++) {
|
||||||
c = in.charAt(i);
|
c = in.charAt(i);
|
||||||
if ((c != '"') && (c != '/') && (c != ':') && (c != '?')) {
|
if ((c == '"') || (c == '/') || (c == ':') || (c == '?')) {
|
||||||
|
} else {
|
||||||
out.append(c);
|
out.append(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,16 +269,11 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
|
|||||||
// need not set out-of-sync: either remove did set, or nothing was removed
|
// need not set out-of-sync: either remove did set, or nothing was removed
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeIf(Predicate<T> filter) {
|
public void removeIf(Predicate<T> test) {
|
||||||
items.keySet().removeIf(filter);
|
for (final T item : items.keySet()) {
|
||||||
}
|
if (test.test(item))
|
||||||
|
remove(item);
|
||||||
public void retainIf(Predicate<T> filter) {
|
}
|
||||||
items.keySet().removeIf(filter.negate());
|
|
||||||
}
|
|
||||||
|
|
||||||
public T find(Predicate<T> filter) {
|
|
||||||
return items.keySet().stream().filter(filter).findFirst().orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
@@ -290,19 +285,4 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
|
|||||||
return (obj instanceof ItemPool ip) &&
|
return (obj instanceof ItemPool ip) &&
|
||||||
(this.items.equals(ip.items));
|
(this.items.equals(ip.items));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies a predicate to this ItemPool's entries.
|
|
||||||
*
|
|
||||||
* @param predicate the Predicate to apply to this ItemPool
|
|
||||||
* @return a new ItemPool made from this ItemPool with only the items that agree with the provided Predicate
|
|
||||||
*/
|
|
||||||
public ItemPool<T> getFilteredPool(Predicate<T> predicate) {
|
|
||||||
ItemPool<T> filteredPool = new ItemPool<>(myClass);
|
|
||||||
for (T c : this.items.keySet()) {
|
|
||||||
if (predicate.test(c))
|
|
||||||
filteredPool.add(c, this.items.get(c));
|
|
||||||
}
|
|
||||||
return filteredPool;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.sentry</groupId>
|
<groupId>io.sentry</groupId>
|
||||||
<artifactId>sentry-logback</artifactId>
|
<artifactId>sentry-logback</artifactId>
|
||||||
<version>8.21.1</version>
|
<version>8.18.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jgrapht</groupId>
|
<groupId>org.jgrapht</groupId>
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
|
|
||||||
/** Keys of descriptive (text) parameters. */
|
/** Keys of descriptive (text) parameters. */
|
||||||
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
||||||
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription")
|
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription").build();
|
||||||
.add("ChangeTypeDesc")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys that should not changed
|
* Keys that should not changed
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class ForgeScript {
|
|||||||
boolean withSource = property.endsWith("Source");
|
boolean withSource = property.endsWith("Source");
|
||||||
final ColorSet colors;
|
final ColorSet colors;
|
||||||
if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) {
|
if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) {
|
||||||
colors = ColorSet.NO_COLORS;
|
colors = ColorSet.getNullColor();
|
||||||
} else {
|
} else {
|
||||||
colors = cardState.getCard().getColor(cardState);
|
colors = cardState.getCard().getColor(cardState);
|
||||||
}
|
}
|
||||||
@@ -166,6 +166,8 @@ public class ForgeScript {
|
|||||||
Card source, CardTraitBase spellAbility) {
|
Card source, CardTraitBase spellAbility) {
|
||||||
if (property.equals("ManaAbility")) {
|
if (property.equals("ManaAbility")) {
|
||||||
return sa.isManaAbility();
|
return sa.isManaAbility();
|
||||||
|
} else if (property.equals("nonManaAbility")) {
|
||||||
|
return !sa.isManaAbility();
|
||||||
} else if (property.equals("withoutXCost")) {
|
} else if (property.equals("withoutXCost")) {
|
||||||
return !sa.costHasManaX();
|
return !sa.costHasManaX();
|
||||||
} else if (property.startsWith("XCost")) {
|
} else if (property.startsWith("XCost")) {
|
||||||
@@ -193,7 +195,7 @@ public class ForgeScript {
|
|||||||
return sa.isKeyword(Keyword.SADDLE);
|
return sa.isKeyword(Keyword.SADDLE);
|
||||||
} else if (property.equals("Station")) {
|
} else if (property.equals("Station")) {
|
||||||
return sa.isKeyword(Keyword.STATION);
|
return sa.isKeyword(Keyword.STATION);
|
||||||
} else if (property.equals("Cycling")) {
|
}else if (property.equals("Cycling")) {
|
||||||
return sa.isCycling();
|
return sa.isCycling();
|
||||||
} else if (property.equals("Dash")) {
|
} else if (property.equals("Dash")) {
|
||||||
return sa.isDash();
|
return sa.isDash();
|
||||||
@@ -235,8 +237,6 @@ public class ForgeScript {
|
|||||||
return sa.isBoast();
|
return sa.isBoast();
|
||||||
} else if (property.equals("Exhaust")) {
|
} else if (property.equals("Exhaust")) {
|
||||||
return sa.isExhaust();
|
return sa.isExhaust();
|
||||||
} else if (property.equals("Mayhem")) {
|
|
||||||
return sa.isMayhem();
|
|
||||||
} else if (property.equals("Mutate")) {
|
} else if (property.equals("Mutate")) {
|
||||||
return sa.isMutate();
|
return sa.isMutate();
|
||||||
} else if (property.equals("Ninjutsu")) {
|
} else if (property.equals("Ninjutsu")) {
|
||||||
@@ -410,8 +410,6 @@ public class ForgeScript {
|
|||||||
return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed();
|
return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if(property.startsWith("NamedAbility")) {
|
|
||||||
return sa.getName().equals(property.substring(12));
|
|
||||||
} else if (sa.getHostCard() != null) {
|
} else if (sa.getHostCard() != null) {
|
||||||
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,6 +414,19 @@ public class Game {
|
|||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the nonactive players who are still fighting to win, in turn order.
|
||||||
|
*/
|
||||||
|
public final PlayerCollection getNonactivePlayers() {
|
||||||
|
// Don't use getPlayersInTurnOrder to prevent copying the player collection twice
|
||||||
|
final PlayerCollection players = new PlayerCollection(ingamePlayers);
|
||||||
|
players.remove(phaseHandler.getPlayerTurn());
|
||||||
|
if (!getTurnOrder().isDefaultDirection()) {
|
||||||
|
Collections.reverse(players);
|
||||||
|
}
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the players who participated in match (regardless of outcome).
|
* Gets the players who participated in match (regardless of outcome).
|
||||||
* <i>Use this in UI and after match calculations</i>
|
* <i>Use this in UI and after match calculations</i>
|
||||||
@@ -845,8 +858,6 @@ public class Game {
|
|||||||
p.revealFaceDownCards();
|
p.revealFaceDownCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO free any mindslaves
|
|
||||||
|
|
||||||
for (Card c : cards) {
|
for (Card c : cards) {
|
||||||
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
||||||
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
||||||
@@ -882,6 +893,8 @@ public class Game {
|
|||||||
}
|
}
|
||||||
triggerList.put(c.getZone().getZoneType(), null, c);
|
triggerList.put(c.getZone().getZoneType(), null, c);
|
||||||
getAction().ceaseToExist(c, false);
|
getAction().ceaseToExist(c, false);
|
||||||
|
// CR 603.2f owner of trigger source lost game
|
||||||
|
getTriggerHandler().clearDelayedTrigger(c);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// return stolen permanents
|
// return stolen permanents
|
||||||
|
|||||||
@@ -57,8 +57,6 @@ import forge.item.PaperCard;
|
|||||||
import forge.util.*;
|
import forge.util.*;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
import io.sentry.Breadcrumb;
|
|
||||||
import io.sentry.Sentry;
|
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.jgrapht.alg.cycle.SzwarcfiterLauerSimpleCycles;
|
import org.jgrapht.alg.cycle.SzwarcfiterLauerSimpleCycles;
|
||||||
import org.jgrapht.graph.DefaultDirectedGraph;
|
import org.jgrapht.graph.DefaultDirectedGraph;
|
||||||
@@ -222,6 +220,10 @@ public class GameAction {
|
|||||||
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
|
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c.isTransformed()) {
|
||||||
|
copied.incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
||||||
copied.setCastSA(cause);
|
copied.setCastSA(cause);
|
||||||
copied.setSplitStateToPlayAbility(cause);
|
copied.setSplitStateToPlayAbility(cause);
|
||||||
@@ -751,29 +753,26 @@ public class GameAction {
|
|||||||
|
|
||||||
public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map<AbilityKey, Object> params) {
|
public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map<AbilityKey, Object> params) {
|
||||||
// Call specific functions to set PlayerZone, then move onto moveTo
|
// Call specific functions to set PlayerZone, then move onto moveTo
|
||||||
try {
|
switch(name) {
|
||||||
return switch (name) {
|
case Hand: return moveToHand(c, cause, params);
|
||||||
case Hand -> moveToHand(c, cause, params);
|
case Library: return moveToLibrary(c, libPosition, cause, params);
|
||||||
case Library -> moveToLibrary(c, libPosition, cause, params);
|
case Battlefield: return moveToPlay(c, c.getController(), cause, params);
|
||||||
case Battlefield -> moveToPlay(c, c.getController(), cause, params);
|
case Graveyard: return moveToGraveyard(c, cause, params);
|
||||||
case Graveyard -> moveToGraveyard(c, cause, params);
|
case Exile:
|
||||||
case Exile -> !c.canExiledBy(cause, true) ? null : exile(c, cause, params);
|
if (!c.canExiledBy(cause, true)) {
|
||||||
case Stack -> moveToStack(c, cause, params);
|
return null;
|
||||||
case PlanarDeck, SchemeDeck, AttractionDeck, ContraptionDeck -> moveToVariantDeck(c, name, libPosition, cause, params);
|
}
|
||||||
case Junkyard -> moveToJunkyard(c, cause, params);
|
return exile(c, cause, params);
|
||||||
default -> moveTo(c.getOwner().getZone(name), c, cause); // sideboard will also get there
|
case Stack: return moveToStack(c, cause, params);
|
||||||
};
|
case PlanarDeck:
|
||||||
} catch (Exception e) {
|
case SchemeDeck:
|
||||||
String msg = "GameAction:moveTo: Exception occured";
|
case AttractionDeck:
|
||||||
|
case ContraptionDeck:
|
||||||
Breadcrumb bread = new Breadcrumb(msg);
|
return moveToVariantDeck(c, name, libPosition, cause, params);
|
||||||
bread.setData("Card", c.getName());
|
case Junkyard:
|
||||||
bread.setData("SA", cause.toString());
|
return moveToJunkyard(c, cause, params);
|
||||||
bread.setData("ZoneType", name.name());
|
default: // sideboard will also get there
|
||||||
bread.setData("Player", c.getOwner());
|
return moveTo(c.getOwner().getZone(name), c, cause);
|
||||||
Sentry.addBreadcrumb(bread);
|
|
||||||
|
|
||||||
throw new RuntimeException("Error in GameAction moveTo " + c.getName() + " to Player Zone " + name.name(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,7 +974,6 @@ public class GameAction {
|
|||||||
// in some corner cases there's no zone yet (copied spell that failed targeting)
|
// in some corner cases there's no zone yet (copied spell that failed targeting)
|
||||||
if (z != null) {
|
if (z != null) {
|
||||||
z.remove(c);
|
z.remove(c);
|
||||||
c.setZone(c.getOwner().getZone(ZoneType.None));
|
|
||||||
if (z.is(ZoneType.Battlefield)) {
|
if (z.is(ZoneType.Battlefield)) {
|
||||||
c.runLeavesPlayCommands();
|
c.runLeavesPlayCommands();
|
||||||
}
|
}
|
||||||
@@ -1602,7 +1600,9 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// recheck the game over condition at this point to make sure no other win conditions apply now.
|
// recheck the game over condition at this point to make sure no other win conditions apply now.
|
||||||
checkGameOverCondition();
|
if (!game.isGameOver()) {
|
||||||
|
checkGameOverCondition();
|
||||||
|
}
|
||||||
|
|
||||||
if (game.getAge() != GameStage.Play) {
|
if (game.getAge() != GameStage.Play) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1822,8 +1822,8 @@ public class GameAction {
|
|||||||
|
|
||||||
private boolean stateBasedAction704_5q(Card c) {
|
private boolean stateBasedAction704_5q(Card c) {
|
||||||
boolean checkAgain = false;
|
boolean checkAgain = false;
|
||||||
final CounterType p1p1 = CounterEnumType.P1P1;
|
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||||
final CounterType m1m1 = CounterEnumType.M1M1;
|
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||||
int plusOneCounters = c.getCounters(p1p1);
|
int plusOneCounters = c.getCounters(p1p1);
|
||||||
int minusOneCounters = c.getCounters(m1m1);
|
int minusOneCounters = c.getCounters(m1m1);
|
||||||
if (plusOneCounters > 0 && minusOneCounters > 0) {
|
if (plusOneCounters > 0 && minusOneCounters > 0) {
|
||||||
@@ -1843,7 +1843,7 @@ public class GameAction {
|
|||||||
return checkAgain;
|
return checkAgain;
|
||||||
}
|
}
|
||||||
private boolean stateBasedAction704_5r(Card c) {
|
private boolean stateBasedAction704_5r(Card c) {
|
||||||
final CounterType dreamType = CounterEnumType.DREAM;
|
final CounterType dreamType = CounterType.get(CounterEnumType.DREAM);
|
||||||
|
|
||||||
int old = c.getCounters(dreamType);
|
int old = c.getCounters(dreamType);
|
||||||
if (old <= 0) {
|
if (old <= 0) {
|
||||||
@@ -1883,10 +1883,6 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void checkGameOverCondition() {
|
public void checkGameOverCondition() {
|
||||||
if (game.isGameOver()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// award loses as SBE
|
// award loses as SBE
|
||||||
GameEndReason reason = null;
|
GameEndReason reason = null;
|
||||||
List<Player> losers = null;
|
List<Player> losers = null;
|
||||||
@@ -2226,13 +2222,6 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
|
|
||||||
// Notify players
|
|
||||||
for (Player p : game.getPlayers()) {
|
|
||||||
p.getController().revealUnsupported(unsupported);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Delivers a message to all players. (use reveal to show Cards) */
|
/** Delivers a message to all players. (use reveal to show Cards) */
|
||||||
public void notifyOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) {
|
public void notifyOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) {
|
||||||
if (saSource != null) {
|
if (saSource != null) {
|
||||||
|
|||||||
@@ -125,22 +125,10 @@ public final class GameActionUtil {
|
|||||||
|
|
||||||
// need to be done there before static abilities does reset the card
|
// need to be done there before static abilities does reset the card
|
||||||
// These Keywords depend on the Mana Cost of for Split Cards
|
// These Keywords depend on the Mana Cost of for Split Cards
|
||||||
if (sa.isBasicSpell()) {
|
if (sa.isBasicSpell() && !sa.isLandAbility()) {
|
||||||
for (final KeywordInterface inst : source.getKeywords()) {
|
for (final KeywordInterface inst : source.getKeywords()) {
|
||||||
final String keyword = inst.getOriginal();
|
final String keyword = inst.getOriginal();
|
||||||
|
|
||||||
if (keyword.startsWith("Mayhem")) {
|
|
||||||
if (!source.isInZone(ZoneType.Graveyard) || !source.wasDiscarded() || !source.enteredThisTurn()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Mayhem));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.isLandAbility()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyword.startsWith("Escape")) {
|
if (keyword.startsWith("Escape")) {
|
||||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -178,6 +166,18 @@ public final class GameActionUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Flashback));
|
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Flashback));
|
||||||
|
} else if (keyword.startsWith("Mayhem")) {
|
||||||
|
if (!source.isInZone(ZoneType.Graveyard) || !source.wasDiscarded() || !source.enteredThisTurn()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if source has No Mana cost, and Mayhem doesn't have own one,
|
||||||
|
// Mayhem can't work
|
||||||
|
if (keyword.equals("Mayhem") && source.getManaCost().isNoCost()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Mayhem));
|
||||||
} else if (keyword.startsWith("Harmonize")) {
|
} else if (keyword.startsWith("Harmonize")) {
|
||||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -242,7 +242,6 @@ public final class GameActionUtil {
|
|||||||
}
|
}
|
||||||
stackCopy.setLastKnownZone(game.getStackZone());
|
stackCopy.setLastKnownZone(game.getStackZone());
|
||||||
stackCopy.setCastFrom(oldZone);
|
stackCopy.setCastFrom(oldZone);
|
||||||
stackCopy.setCastSA(sa);
|
|
||||||
lkicheck = true;
|
lkicheck = true;
|
||||||
|
|
||||||
stackCopy.clearStaticChangedCardKeywords(false);
|
stackCopy.clearStaticChangedCardKeywords(false);
|
||||||
@@ -993,6 +992,9 @@ public final class GameActionUtil {
|
|||||||
oldCard.setBackSide(false);
|
oldCard.setBackSide(false);
|
||||||
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
||||||
oldCard.unanimateBestow();
|
oldCard.unanimateBestow();
|
||||||
|
if (ability.isDisturb() || ability.hasParam("CastTransformed")) {
|
||||||
|
oldCard.undoIncrementTransformedTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
if (ability.hasParam("Prototype")) {
|
if (ability.hasParam("Prototype")) {
|
||||||
oldCard.removeCloneState(oldCard.getPrototypeTimestamp());
|
oldCard.removeCloneState(oldCard.getPrototypeTimestamp());
|
||||||
|
|||||||
@@ -33,18 +33,17 @@ import forge.game.card.CardCollection;
|
|||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.event.GameEventCardAttachment;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.keyword.KeywordWithType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
|
||||||
import forge.game.staticability.StaticAbilityCantAttach;
|
import forge.game.staticability.StaticAbilityCantAttach;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Lang;
|
|
||||||
|
|
||||||
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||||
protected int id;
|
protected int id;
|
||||||
@@ -198,12 +197,14 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
public final void addAttachedCard(final Card c) {
|
public final void addAttachedCard(final Card c) {
|
||||||
if (attachedCards.add(c)) {
|
if (attachedCards.add(c)) {
|
||||||
updateAttachedCards();
|
updateAttachedCards();
|
||||||
|
getGame().fireEvent(new GameEventCardAttachment(c, null, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void removeAttachedCard(final Card c) {
|
public final void removeAttachedCard(final Card c) {
|
||||||
if (attachedCards.remove(c)) {
|
if (attachedCards.remove(c)) {
|
||||||
updateAttachedCards();
|
updateAttachedCards();
|
||||||
|
getGame().fireEvent(new GameEventCardAttachment(c, this, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,83 +222,63 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
return canBeAttached(attach, sa, false);
|
return canBeAttached(attach, sa, false);
|
||||||
}
|
}
|
||||||
public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) {
|
public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) {
|
||||||
return cantBeAttachedMsg(attach, sa, checkSBA) == null;
|
// master mode
|
||||||
}
|
if (!attach.isAttachment() || (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE))
|
||||||
|
|| equals(attach)) {
|
||||||
public String cantBeAttachedMsg(final Card attach, SpellAbility sa) {
|
return false;
|
||||||
return cantBeAttachedMsg(attach, sa, false);
|
|
||||||
}
|
|
||||||
public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) {
|
|
||||||
if (!attach.isAttachment()) {
|
|
||||||
return attach.getName() + " is not an attachment";
|
|
||||||
}
|
|
||||||
if (equals(attach)) {
|
|
||||||
return attach.getName() + " can't attach to itself";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE)) {
|
|
||||||
return attach.getName() + " is a creature without reconfigure";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attach.isPhasedOut()) {
|
if (attach.isPhasedOut()) {
|
||||||
return attach.getName() + " is phased out";
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attach.isAura()) {
|
// check for rules
|
||||||
String msg = cantBeEnchantedByMsg(attach);
|
if (attach.isAura() && !canBeEnchantedBy(attach)) {
|
||||||
if (msg != null) {
|
return false;
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (attach.isEquipment()) {
|
if (attach.isEquipment() && !canBeEquippedBy(attach, sa)) {
|
||||||
String msg = cantBeEquippedByMsg(attach, sa);
|
return false;
|
||||||
if (msg != null) {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (attach.isFortification()) {
|
if (attach.isFortification() && !canBeFortifiedBy(attach)) {
|
||||||
String msg = cantBeFortifiedByMsg(attach);
|
return false;
|
||||||
if (msg != null) {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StaticAbility stAb = StaticAbilityCantAttach.cantAttach(this, attach, checkSBA);
|
// check for can't attach static
|
||||||
if (stAb != null) {
|
if (StaticAbilityCantAttach.cantAttach(this, attach, checkSBA)) {
|
||||||
return stAb.toString();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// true for all
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String cantBeEquippedByMsg(final Card aura, SpellAbility sa) {
|
protected boolean canBeEquippedBy(final Card aura, SpellAbility sa) {
|
||||||
|
/**
|
||||||
|
* Equip only to Creatures which are cards
|
||||||
|
*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canBeFortifiedBy(final Card aura) {
|
||||||
/**
|
/**
|
||||||
* Equip only to Lands which are cards
|
* Equip only to Lands which are cards
|
||||||
*/
|
*/
|
||||||
return getName() + " is not a Creature";
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String cantBeFortifiedByMsg(final Card fort) {
|
protected boolean canBeEnchantedBy(final Card aura) {
|
||||||
/**
|
|
||||||
* Equip only to Lands which are cards
|
|
||||||
*/
|
|
||||||
return getName() + " is not a Land";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String cantBeEnchantedByMsg(final Card aura) {
|
|
||||||
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
||||||
return "No Enchant Keyword";
|
return false;
|
||||||
}
|
}
|
||||||
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||||
if (ki instanceof KeywordWithType kwt) {
|
String k = ki.getOriginal();
|
||||||
String v = kwt.getValidType();
|
String m[] = k.split(":");
|
||||||
String desc = kwt.getTypeDescription();
|
String v = m[1];
|
||||||
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||||
return getName() + " is not " + Lang.nounWithAmount(1, desc);
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCounters() {
|
public boolean hasCounters() {
|
||||||
@@ -324,6 +305,9 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
Integer value = counters.get(counterName);
|
Integer value = counters.get(counterName);
|
||||||
return value == null ? 0 : value;
|
return value == null ? 0 : value;
|
||||||
}
|
}
|
||||||
|
public final int getCounters(final CounterEnumType counterType) {
|
||||||
|
return getCounters(CounterType.get(counterType));
|
||||||
|
}
|
||||||
|
|
||||||
public void setCounters(final CounterType counterType, final Integer num) {
|
public void setCounters(final CounterType counterType, final Integer num) {
|
||||||
if (num <= 0) {
|
if (num <= 0) {
|
||||||
@@ -332,6 +316,9 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
counters.put(counterType, num);
|
counters.put(counterType, num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void setCounters(final CounterEnumType counterType, final Integer num) {
|
||||||
|
setCounters(CounterType.get(counterType), num);
|
||||||
|
}
|
||||||
|
|
||||||
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
|
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
|
||||||
|
|
||||||
@@ -341,6 +328,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
abstract public int subtractCounter(final CounterType counterName, final int n, final Player remover);
|
abstract public int subtractCounter(final CounterType counterName, final int n, final Player remover);
|
||||||
abstract public void clearCounters();
|
abstract public void clearCounters();
|
||||||
|
|
||||||
|
public boolean canReceiveCounters(final CounterEnumType type) {
|
||||||
|
return canReceiveCounters(CounterType.get(type));
|
||||||
|
}
|
||||||
|
|
||||||
public final void addCounter(final CounterType counterType, int n, final Player source, GameEntityCounterTable table) {
|
public final void addCounter(final CounterType counterType, int n, final Player source, GameEntityCounterTable table) {
|
||||||
if (n <= 0 || !canReceiveCounters(counterType)) {
|
if (n <= 0 || !canReceiveCounters(counterType)) {
|
||||||
// As per rule 107.1b
|
// As per rule 107.1b
|
||||||
@@ -360,7 +351,18 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
table.put(source, this, counterType, n);
|
table.put(source, this, counterType, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void addCounter(final CounterEnumType counterType, final int n, final Player source, GameEntityCounterTable table) {
|
||||||
|
addCounter(CounterType.get(counterType), n, source, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int subtractCounter(final CounterEnumType counterName, final int n, final Player remover) {
|
||||||
|
return subtractCounter(CounterType.get(counterName), n, remover);
|
||||||
|
}
|
||||||
|
|
||||||
abstract public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params);
|
abstract public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params);
|
||||||
|
public void addCounterInternal(final CounterEnumType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params) {
|
||||||
|
addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table, params);
|
||||||
|
}
|
||||||
public Integer getCounterMax(final CounterType counterType) {
|
public Integer getCounterMax(final CounterType counterType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ public enum GameLogEntryType {
|
|||||||
TURN("Turn"),
|
TURN("Turn"),
|
||||||
MULLIGAN("Mulligan"),
|
MULLIGAN("Mulligan"),
|
||||||
ANTE("Ante"),
|
ANTE("Ante"),
|
||||||
DRAFT("Draft"),
|
|
||||||
ZONE_CHANGE("Zone Change"),
|
ZONE_CHANGE("Zone Change"),
|
||||||
PLAYER_CONTROL("Player control"),
|
PLAYER_CONTROL("Player control"),
|
||||||
COMBAT("Combat"),
|
COMBAT("Combat"),
|
||||||
|
|||||||
@@ -29,25 +29,25 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventGameOutcome ev) {
|
public GameLogEntry visit(GameEventGameOutcome ev) {
|
||||||
// Turn number counted from the starting player
|
// Turn number counted from the starting player
|
||||||
int lastTurn = (int)Math.ceil((float)ev.result().getLastTurnNumber() / 2.0);
|
int lastTurn = (int)Math.ceil((float)ev.result.getLastTurnNumber() / 2.0);
|
||||||
log.add(GameLogEntryType.GAME_OUTCOME, localizer.getMessage("lblTurn") + " " + lastTurn);
|
log.add(GameLogEntryType.GAME_OUTCOME, localizer.getMessage("lblTurn") + " " + lastTurn);
|
||||||
|
|
||||||
for (String outcome : ev.result().getOutcomeStrings()) {
|
for (String outcome : ev.result.getOutcomeStrings()) {
|
||||||
log.add(GameLogEntryType.GAME_OUTCOME, outcome);
|
log.add(GameLogEntryType.GAME_OUTCOME, outcome);
|
||||||
}
|
}
|
||||||
return generateSummary(ev.history());
|
return generateSummary(ev.history);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventScry ev) {
|
public GameLogEntry visit(GameEventScry ev) {
|
||||||
String scryOutcome = "";
|
String scryOutcome = "";
|
||||||
|
|
||||||
if (ev.toTop() > 0 && ev.toBottom() > 0) {
|
if (ev.toTop > 0 && ev.toBottom > 0) {
|
||||||
scryOutcome = localizer.getMessage("lblLogScryTopBottomLibrary").replace("%s", ev.player().toString()).replace("%top", String.valueOf(ev.toTop())).replace("%bottom", String.valueOf(ev.toBottom()));
|
scryOutcome = localizer.getMessage("lblLogScryTopBottomLibrary").replace("%s", ev.player.toString()).replace("%top", String.valueOf(ev.toTop)).replace("%bottom", String.valueOf(ev.toBottom));
|
||||||
} else if (ev.toBottom() == 0) {
|
} else if (ev.toBottom == 0) {
|
||||||
scryOutcome = localizer.getMessage("lblLogScryTopLibrary").replace("%s", ev.player().toString()).replace("%top", String.valueOf(ev.toTop()));
|
scryOutcome = localizer.getMessage("lblLogScryTopLibrary").replace("%s", ev.player.toString()).replace("%top", String.valueOf(ev.toTop));
|
||||||
} else {
|
} else {
|
||||||
scryOutcome = localizer.getMessage("lblLogScryBottomLibrary").replace("%s", ev.player().toString()).replace("%bottom", String.valueOf(ev.toBottom()));
|
scryOutcome = localizer.getMessage("lblLogScryBottomLibrary").replace("%s", ev.player.toString()).replace("%bottom", String.valueOf(ev.toBottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, scryOutcome);
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, scryOutcome);
|
||||||
@@ -57,12 +57,12 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
public GameLogEntry visit(GameEventSurveil ev) {
|
public GameLogEntry visit(GameEventSurveil ev) {
|
||||||
String surveilOutcome = "";
|
String surveilOutcome = "";
|
||||||
|
|
||||||
if (ev.toLibrary() > 0 && ev.toGraveyard() > 0) {
|
if (ev.toLibrary > 0 && ev.toGraveyard > 0) {
|
||||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibraryGraveyard", ev.player().toString(), String.valueOf(ev.toLibrary()), String.valueOf(ev.toGraveyard()));
|
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibraryGraveyard", ev.player.toString(), String.valueOf(ev.toLibrary), String.valueOf(ev.toGraveyard));
|
||||||
} else if (ev.toGraveyard() == 0) {
|
} else if (ev.toGraveyard == 0) {
|
||||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibrary", ev.player().toString(), String.valueOf(ev.toLibrary()));
|
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibrary", ev.player.toString(), String.valueOf(ev.toLibrary));
|
||||||
} else {
|
} else {
|
||||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToGraveyard", ev.player().toString(), String.valueOf(ev.toGraveyard()));
|
surveilOutcome = localizer.getMessage("lblLogSurveiledToGraveyard", ev.player.toString(), String.valueOf(ev.toGraveyard));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, surveilOutcome);
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, surveilOutcome);
|
||||||
@@ -70,26 +70,26 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventSpellResolved ev) {
|
public GameLogEntry visit(GameEventSpellResolved ev) {
|
||||||
String messageForLog = ev.hasFizzled() ? localizer.getMessage("lblLogCardAbilityFizzles", ev.spell().getHostCard().toString()) : ev.spell().getStackDescription();
|
String messageForLog = ev.hasFizzled ? localizer.getMessage("lblLogCardAbilityFizzles", ev.spell.getHostCard().toString()) : ev.spell.getStackDescription();
|
||||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, messageForLog);
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, messageForLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventSpellAbilityCast event) {
|
public GameLogEntry visit(GameEventSpellAbilityCast event) {
|
||||||
String player = event.sa().getActivatingPlayer().getName();
|
String player = event.sa.getActivatingPlayer().getName();
|
||||||
String action = event.sa().isSpell() ? localizer.getMessage("lblCast")
|
String action = event.sa.isSpell() ? localizer.getMessage("lblCast")
|
||||||
: event.sa().isTrigger() ? localizer.getMessage("lblTriggered")
|
: event.sa.isTrigger() ? localizer.getMessage("lblTriggered")
|
||||||
: localizer.getMessage("lblActivated");
|
: localizer.getMessage("lblActivated");
|
||||||
String object = event.si().getStackDescription().startsWith("Morph ")
|
String object = event.si.getStackDescription().startsWith("Morph ")
|
||||||
? localizer.getMessage("lblMorph")
|
? localizer.getMessage("lblMorph")
|
||||||
: event.sa().getHostCard().toString();
|
: event.sa.getHostCard().toString();
|
||||||
|
|
||||||
String messageForLog = "";
|
String messageForLog = "";
|
||||||
|
|
||||||
if (event.sa().getTargetRestrictions() != null) {
|
if (event.sa.getTargetRestrictions() != null) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
for (TargetChoices ch : event.sa().getAllTargetChoices()) {
|
for (TargetChoices ch : event.sa.getAllTargetChoices()) {
|
||||||
if (null != ch) {
|
if (null != ch) {
|
||||||
sb.append(ch);
|
sb.append(ch);
|
||||||
}
|
}
|
||||||
@@ -104,18 +104,18 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventCardModeChosen ev) {
|
public GameLogEntry visit(GameEventCardModeChosen ev) {
|
||||||
if (!ev.log()) {
|
if (!ev.log) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String modeChoiceOutcome;
|
String modeChoiceOutcome;
|
||||||
if (ev.random()) {
|
if (ev.random) {
|
||||||
modeChoiceOutcome = localizer.getMessage("lblLogRandomMode", ev.cardName(), ev.mode());
|
modeChoiceOutcome = localizer.getMessage("lblLogRandomMode", ev.cardName, ev.mode);
|
||||||
} else {
|
} else {
|
||||||
modeChoiceOutcome = localizer.getMessage("lblLogPlayerChosenModeForCard",
|
modeChoiceOutcome = localizer.getMessage("lblLogPlayerChosenModeForCard",
|
||||||
ev.player().toString(), ev.mode(), ev.cardName());
|
ev.player.toString(), ev.mode, ev.cardName);
|
||||||
}
|
}
|
||||||
String name = CardTranslation.getTranslatedName(ev.cardName());
|
String name = CardTranslation.getTranslatedName(ev.cardName);
|
||||||
modeChoiceOutcome = TextUtil.fastReplace(modeChoiceOutcome, "CARDNAME", name);
|
modeChoiceOutcome = TextUtil.fastReplace(modeChoiceOutcome, "CARDNAME", name);
|
||||||
modeChoiceOutcome = TextUtil.fastReplace(modeChoiceOutcome, "NICKNAME",
|
modeChoiceOutcome = TextUtil.fastReplace(modeChoiceOutcome, "NICKNAME",
|
||||||
Lang.getInstance().getNickName(name));
|
Lang.getInstance().getNickName(name));
|
||||||
@@ -124,7 +124,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventRandomLog ev) {
|
public GameLogEntry visit(GameEventRandomLog ev) {
|
||||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, ev.message());
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, ev.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameLogEntry generateSummary(final Collection<GameOutcome> gamesPlayed) {
|
private static GameLogEntry generateSummary(final Collection<GameOutcome> gamesPlayed) {
|
||||||
@@ -152,8 +152,8 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(final GameEventPlayerControl event) {
|
public GameLogEntry visit(final GameEventPlayerControl event) {
|
||||||
final LobbyPlayer newLobbyPlayer = event.newLobbyPlayer();
|
final LobbyPlayer newLobbyPlayer = event.newLobbyPlayer;
|
||||||
final Player p = event.player();
|
final Player p = event.player;
|
||||||
|
|
||||||
final String message;
|
final String message;
|
||||||
if (newLobbyPlayer == null) {
|
if (newLobbyPlayer == null) {
|
||||||
@@ -166,23 +166,23 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventTurnPhase ev) {
|
public GameLogEntry visit(GameEventTurnPhase ev) {
|
||||||
Player p = ev.playerTurn();
|
Player p = ev.playerTurn;
|
||||||
return new GameLogEntry(GameLogEntryType.PHASE, ev.phaseDesc() + Lang.getInstance().getPossessedObject(p.getName(), ev.phase().nameForUi));
|
return new GameLogEntry(GameLogEntryType.PHASE, ev.phaseDesc + Lang.getInstance().getPossessedObject(p.getName(), ev.phase.nameForUi));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventCardDamaged event) {
|
public GameLogEntry visit(GameEventCardDamaged event) {
|
||||||
String additionalLog = "";
|
String additionalLog = "";
|
||||||
if (event.type() == DamageType.Deathtouch) {
|
if (event.type == DamageType.Deathtouch) {
|
||||||
additionalLog = localizer.getMessage("lblDeathtouch");
|
additionalLog = localizer.getMessage("lblDeathtouch");
|
||||||
}
|
}
|
||||||
if (event.type() == DamageType.M1M1Counters) {
|
if (event.type == DamageType.M1M1Counters) {
|
||||||
additionalLog = localizer.getMessage("lblAsM1M1Counters");
|
additionalLog = localizer.getMessage("lblAsM1M1Counters");
|
||||||
}
|
}
|
||||||
if (event.type() == DamageType.LoyaltyLoss) {
|
if (event.type == DamageType.LoyaltyLoss) {
|
||||||
additionalLog = localizer.getMessage("lblRemovingNLoyaltyCounter", String.valueOf(event.amount()));
|
additionalLog = localizer.getMessage("lblRemovingNLoyaltyCounter", String.valueOf(event.amount));
|
||||||
}
|
}
|
||||||
String message = localizer.getMessage("lblSourceDealsNDamageToDest", event.source().toString(), String.valueOf(event.amount()), additionalLog, event.card().toString());
|
String message = localizer.getMessage("lblSourceDealsNDamageToDest", event.source.toString(), String.valueOf(event.amount), additionalLog, event.card.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,43 +191,43 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventLandPlayed ev) {
|
public GameLogEntry visit(GameEventLandPlayed ev) {
|
||||||
String message = localizer.getMessage("lblLogPlayerPlayedLand", ev.player().toString(), ev.land().toString());
|
String message = localizer.getMessage("lblLogPlayerPlayedLand", ev.player.toString(), ev.land.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.LAND, message);
|
return new GameLogEntry(GameLogEntryType.LAND, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventTurnBegan event) {
|
public GameLogEntry visit(GameEventTurnBegan event) {
|
||||||
String message = localizer.getMessage("lblLogTurnNOwnerByPlayer", String.valueOf(event.turnNumber()), event.turnOwner().toString());
|
String message = localizer.getMessage("lblLogTurnNOwnerByPlayer", String.valueOf(event.turnNumber), event.turnOwner.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.TURN, message);
|
return new GameLogEntry(GameLogEntryType.TURN, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventPlayerDamaged ev) {
|
public GameLogEntry visit(GameEventPlayerDamaged ev) {
|
||||||
String extra = ev.infect() ? localizer.getMessage("lblLogAsPoisonCounters") : "";
|
String extra = ev.infect ? localizer.getMessage("lblLogAsPoisonCounters") : "";
|
||||||
String damageType = ev.combat() ? localizer.getMessage("lblCombat") : localizer.getMessage("lblNonCombat");
|
String damageType = ev.combat ? localizer.getMessage("lblCombat") : localizer.getMessage("lblNonCombat");
|
||||||
String message = localizer.getMessage("lblLogSourceDealsNDamageOfTypeToDest", ev.source().toString(),
|
String message = localizer.getMessage("lblLogSourceDealsNDamageOfTypeToDest", ev.source.toString(),
|
||||||
String.valueOf(ev.amount()), damageType, ev.target().toString(), extra);
|
String.valueOf(ev.amount), damageType, ev.target.toString(), extra);
|
||||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventPlayerPoisoned ev) {
|
public GameLogEntry visit(GameEventPlayerPoisoned ev) {
|
||||||
String message = localizer.getMessage("lblLogPlayerReceivesNPosionCounterFrom",
|
String message = localizer.getMessage("lblLogPlayerReceivesNPosionCounterFrom",
|
||||||
ev.receiver().toString(), String.valueOf(ev.amount()), ev.source().toString());
|
ev.receiver.toString(), String.valueOf(ev.amount), ev.source.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventPlayerRadiation ev) {
|
public GameLogEntry visit(GameEventPlayerRadiation ev) {
|
||||||
String message;
|
String message;
|
||||||
final int change = ev.change();
|
final int change = ev.change;
|
||||||
String radCtr = CounterEnumType.RAD.getName().toLowerCase() + " " +
|
String radCtr = CounterEnumType.RAD.getName().toLowerCase() + " " +
|
||||||
Localizer.getInstance().getMessage("lblCounter").toLowerCase();
|
Localizer.getInstance().getMessage("lblCounter").toLowerCase();
|
||||||
if (change >= 0) message = localizer.getMessage("lblLogPlayerRadiation",
|
if (change >= 0) message = localizer.getMessage("lblLogPlayerRadiation",
|
||||||
ev.receiver().toString(), Lang.nounWithNumeralExceptOne(String.valueOf(change), radCtr),
|
ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(change), radCtr),
|
||||||
ev.source().toString());
|
ev.source.toString());
|
||||||
else message = localizer.getMessage("lblLogPlayerRadRemove",
|
else message = localizer.getMessage("lblLogPlayerRadRemove",
|
||||||
ev.receiver().toString(), Lang.nounWithNumeralExceptOne(String.valueOf(Math.abs(change)), radCtr));
|
ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(Math.abs(change)), radCtr));
|
||||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,16 +239,16 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
// Append Defending Player/Planeswalker
|
// Append Defending Player/Planeswalker
|
||||||
|
|
||||||
// Not a big fan of the triple nested loop here
|
// Not a big fan of the triple nested loop here
|
||||||
for (GameEntity k : ev.attackersMap().keySet()) {
|
for (GameEntity k : ev.attackersMap.keySet()) {
|
||||||
Collection<Card> attackers = ev.attackersMap().get(k);
|
Collection<Card> attackers = ev.attackersMap.get(k);
|
||||||
if (attackers == null || attackers.isEmpty()) {
|
if (attackers == null || attackers.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (sb.length() > 0) sb.append("\n");
|
if (sb.length() > 0) sb.append("\n");
|
||||||
sb.append(localizer.getMessage("lblLogPlayerAssignedAttackerToAttackTarget", ev.player(), Lang.joinHomogenous(attackers), k));
|
sb.append(localizer.getMessage("lblLogPlayerAssignedAttackerToAttackTarget", ev.player, Lang.joinHomogenous(attackers), k));
|
||||||
}
|
}
|
||||||
if (sb.length() == 0) {
|
if (sb.length() == 0) {
|
||||||
sb.append(localizer.getMessage("lblPlayerDidntAttackThisTurn").replace("%s", ev.player().toString()));
|
sb.append(localizer.getMessage("lblPlayerDidntAttackThisTurn").replace("%s", ev.player.toString()));
|
||||||
}
|
}
|
||||||
return new GameLogEntry(GameLogEntryType.COMBAT, sb.toString());
|
return new GameLogEntry(GameLogEntryType.COMBAT, sb.toString());
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
Collection<Card> blockers = null;
|
Collection<Card> blockers = null;
|
||||||
|
|
||||||
for (Entry<GameEntity, MapOfLists<Card, Card>> kv : ev.blockers().entrySet()) {
|
for (Entry<GameEntity, MapOfLists<Card, Card>> kv : ev.blockers.entrySet()) {
|
||||||
GameEntity defender = kv.getKey();
|
GameEntity defender = kv.getKey();
|
||||||
MapOfLists<Card, Card> attackers = kv.getValue();
|
MapOfLists<Card, Card> attackers = kv.getValue();
|
||||||
if (attackers == null || attackers.isEmpty()) {
|
if (attackers == null || attackers.isEmpty()) {
|
||||||
@@ -298,7 +298,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameLogEntry visit(GameEventMulligan ev) {
|
public GameLogEntry visit(GameEventMulligan ev) {
|
||||||
String message = localizer.getMessage("lblPlayerHasMulliganedDownToNCards").replace("%d", String.valueOf(ev.player().getZone(ZoneType.Hand).size())).replace("%s", ev.player().toString());
|
String message = localizer.getMessage("lblPlayerHasMulliganedDownToNCards").replace("%d", String.valueOf(ev.player.getZone(ZoneType.Hand).size())).replace("%s", ev.player.toString());
|
||||||
return new GameLogEntry(GameLogEntryType.MULLIGAN, message);
|
return new GameLogEntry(GameLogEntryType.MULLIGAN, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ public class GameRules {
|
|||||||
private boolean AISideboardingEnabled = false;
|
private boolean AISideboardingEnabled = false;
|
||||||
private boolean sideboardForAI = false;
|
private boolean sideboardForAI = false;
|
||||||
private final Set<GameType> appliedVariants = EnumSet.noneOf(GameType.class);
|
private final Set<GameType> appliedVariants = EnumSet.noneOf(GameType.class);
|
||||||
private int simTimeout = 120;
|
|
||||||
|
|
||||||
// it's a preference, not rule... but I could hardly find a better place for it
|
// it's a preference, not rule... but I could hardly find a better place for it
|
||||||
private boolean useGrayText;
|
private boolean useGrayText;
|
||||||
@@ -125,12 +124,4 @@ public class GameRules {
|
|||||||
public void setWarnAboutAICards(final boolean warnAboutAICards) {
|
public void setWarnAboutAICards(final boolean warnAboutAICards) {
|
||||||
this.warnAboutAICards = warnAboutAICards;
|
this.warnAboutAICards = warnAboutAICards;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSimTimeout() {
|
|
||||||
return this.simTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSimTimeout(final int duration) {
|
|
||||||
this.simTimeout = duration;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ public enum GameType {
|
|||||||
Tournament (DeckFormat.Constructed, false, true, true, "lblTournament", ""),
|
Tournament (DeckFormat.Constructed, false, true, true, "lblTournament", ""),
|
||||||
CommanderGauntlet (DeckFormat.Commander, false, false, false, "lblCommanderGauntlet", "lblCommanderDesc"),
|
CommanderGauntlet (DeckFormat.Commander, false, false, false, "lblCommanderGauntlet", "lblCommanderDesc"),
|
||||||
Quest (DeckFormat.QuestDeck, true, true, false, "lblQuest", ""),
|
Quest (DeckFormat.QuestDeck, true, true, false, "lblQuest", ""),
|
||||||
QuestCommander (DeckFormat.Commander, true, true, false, "lblQuestCommander", ""),
|
|
||||||
QuestDraft (DeckFormat.Limited, true, true, true, "lblQuestDraft", ""),
|
QuestDraft (DeckFormat.Limited, true, true, true, "lblQuestDraft", ""),
|
||||||
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "lblPlanarConquest", ""),
|
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "lblPlanarConquest", ""),
|
||||||
Adventure (DeckFormat.Adventure, true, false, false, "lblAdventure", ""),
|
Adventure (DeckFormat.Adventure, true, false, false, "lblAdventure", ""),
|
||||||
@@ -72,8 +71,6 @@ public enum GameType {
|
|||||||
return deck;
|
return deck;
|
||||||
});
|
});
|
||||||
|
|
||||||
private static final EnumSet<GameType> DRAFT_FORMATS = EnumSet.of(Draft, QuestDraft, AdventureEvent);
|
|
||||||
|
|
||||||
private final DeckFormat deckFormat;
|
private final DeckFormat deckFormat;
|
||||||
private final boolean isCardPoolLimited, canSideboard, addWonCardsMidGame;
|
private final boolean isCardPoolLimited, canSideboard, addWonCardsMidGame;
|
||||||
private final String name, englishName, description;
|
private final String name, englishName, description;
|
||||||
@@ -90,7 +87,7 @@ public enum GameType {
|
|||||||
addWonCardsMidGame = addWonCardsMidgame0;
|
addWonCardsMidGame = addWonCardsMidgame0;
|
||||||
name = localizer.getMessage(name0);
|
name = localizer.getMessage(name0);
|
||||||
englishName = localizer.getEnglishMessage(name0);
|
englishName = localizer.getEnglishMessage(name0);
|
||||||
if (!description0.isEmpty()) {
|
if (description0.length()>0) {
|
||||||
description0 = localizer.getMessage(description0);
|
description0 = localizer.getMessage(description0);
|
||||||
}
|
}
|
||||||
description = description0;
|
description = description0;
|
||||||
@@ -130,8 +127,19 @@ public enum GameType {
|
|||||||
return addWonCardsMidGame;
|
return addWonCardsMidGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDraft() {
|
public boolean isCommandZoneNeeded() {
|
||||||
return DRAFT_FORMATS.contains(this);
|
return true; //TODO: Figure out way to move command zone into field so it can be hidden when empty
|
||||||
|
/*switch (this) {
|
||||||
|
case Archenemy:
|
||||||
|
case Commander:
|
||||||
|
case Oathbreaker:
|
||||||
|
case TinyLeaders:
|
||||||
|
case Planechase:
|
||||||
|
case Vanguard:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
@@ -145,27 +153,6 @@ public enum GameType {
|
|||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the deck sections used by most decks in this game type.
|
|
||||||
*/
|
|
||||||
public EnumSet<DeckSection> getPrimaryDeckSections() {
|
|
||||||
return deckFormat.getPrimaryDeckSections();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the set of variant card sections that decks for this game type can include.
|
|
||||||
*/
|
|
||||||
public EnumSet<DeckSection> getSupplimentalDeckSections() {
|
|
||||||
if(!deckFormat.getPrimaryDeckSections().contains(DeckSection.Main))
|
|
||||||
return EnumSet.noneOf(DeckSection.class); //Already an extra deck, like a dedicated Scheme or Planar deck.
|
|
||||||
if(deckFormat == DeckFormat.Limited)
|
|
||||||
return EnumSet.of(DeckSection.Conspiracy, DeckSection.Contraptions, DeckSection.Attractions);
|
|
||||||
if(this == Constructed || this == Commander)
|
|
||||||
return EnumSet.of(DeckSection.Avatar, DeckSection.Schemes, DeckSection.Planes, DeckSection.Conspiracy,
|
|
||||||
DeckSection.Attractions, DeckSection.Contraptions);
|
|
||||||
return EnumSet.of(DeckSection.Attractions, DeckSection.Contraptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GameType smartValueOf(String name) {
|
public static GameType smartValueOf(String name) {
|
||||||
return Enums.getIfPresent(GameType.class, name).orNull();
|
return Enums.getIfPresent(GameType.class, name).orNull();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,6 @@ public class GameView extends TrackableObject {
|
|||||||
}
|
}
|
||||||
public void setDependencies(Table<StaticAbility, StaticAbility, Set<StaticAbilityLayer>> dependencies) {
|
public void setDependencies(Table<StaticAbility, StaticAbility, Set<StaticAbilityLayer>> dependencies) {
|
||||||
if (dependencies.isEmpty()) {
|
if (dependencies.isEmpty()) {
|
||||||
set(TrackableProperty.Dependencies, "");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import forge.item.PaperCard;
|
|||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -225,7 +224,6 @@ public class Match {
|
|||||||
// friendliness
|
// friendliness
|
||||||
Map<Player, Map<DeckSection, List<? extends PaperCard>>> rAICards = new HashMap<>();
|
Map<Player, Map<DeckSection, List<? extends PaperCard>>> rAICards = new HashMap<>();
|
||||||
Multimap<Player, PaperCard> removedAnteCards = ArrayListMultimap.create();
|
Multimap<Player, PaperCard> removedAnteCards = ArrayListMultimap.create();
|
||||||
Map<Player, List<PaperCard>> unsupported = new HashMap<>();
|
|
||||||
|
|
||||||
final FCollectionView<Player> players = game.getPlayers();
|
final FCollectionView<Player> players = game.getPlayers();
|
||||||
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
|
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
|
||||||
@@ -290,32 +288,22 @@ public class Match {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Deck toCheck = psc.getDeck();
|
Deck myDeck = psc.getDeck();
|
||||||
if (toCheck == null) {
|
player.setDraftNotes(myDeck.getDraftNotes());
|
||||||
try {
|
|
||||||
System.err.println(psc.getPlayer().getName() + " Deck is NULL...");
|
|
||||||
int val = rules.getGameType().getDeckFormat().getMainRange().getMinimum();
|
|
||||||
toCheck = new Deck("NULL");
|
|
||||||
if (val > 0)
|
|
||||||
toCheck.getMain().add("Wastes", val);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
|
||||||
Pair<Deck, List<PaperCard>> myDeck = toCheck.getValid();
|
|
||||||
player.setDraftNotes(myDeck.getLeft().getDraftNotes());
|
|
||||||
|
|
||||||
Set<PaperCard> myRemovedAnteCards = null;
|
Set<PaperCard> myRemovedAnteCards = null;
|
||||||
if (!rules.useAnte()) {
|
if (!rules.useAnte()) {
|
||||||
myRemovedAnteCards = getRemovedAnteCards(myDeck.getLeft());
|
myRemovedAnteCards = getRemovedAnteCards(myDeck);
|
||||||
for (PaperCard cp: myRemovedAnteCards) {
|
for (PaperCard cp: myRemovedAnteCards) {
|
||||||
for (Entry<DeckSection, CardPool> ds : myDeck.getLeft()) {
|
for (Entry<DeckSection, CardPool> ds : myDeck) {
|
||||||
ds.getValue().removeAll(cp);
|
ds.getValue().removeAll(cp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preparePlayerZone(player, ZoneType.Library, myDeck.getLeft().getMain(), psc.useRandomFoil());
|
preparePlayerZone(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
|
||||||
if (myDeck.getLeft().has(DeckSection.Sideboard)) {
|
if (myDeck.has(DeckSection.Sideboard)) {
|
||||||
preparePlayerZone(player, ZoneType.Sideboard, myDeck.getLeft().get(DeckSection.Sideboard), psc.useRandomFoil());
|
preparePlayerZone(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
|
||||||
|
|
||||||
// Assign Companion
|
// Assign Companion
|
||||||
Card companion = player.assignCompanion(game, person);
|
Card companion = player.assignCompanion(game, person);
|
||||||
@@ -334,7 +322,7 @@ public class Match {
|
|||||||
player.shuffle(null);
|
player.shuffle(null);
|
||||||
|
|
||||||
if (isFirstGame) {
|
if (isFirstGame) {
|
||||||
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck.getLeft());
|
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
|
||||||
if (cardsComplained != null && !cardsComplained.isEmpty()) {
|
if (cardsComplained != null && !cardsComplained.isEmpty()) {
|
||||||
rAICards.put(player, cardsComplained);
|
rAICards.put(player, cardsComplained);
|
||||||
}
|
}
|
||||||
@@ -349,7 +337,6 @@ public class Match {
|
|||||||
if (myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty()) {
|
if (myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty()) {
|
||||||
removedAnteCards.putAll(player, myRemovedAnteCards);
|
removedAnteCards.putAll(player, myRemovedAnteCards);
|
||||||
}
|
}
|
||||||
unsupported.put(player, myDeck.getRight());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Localizer localizer = Localizer.getInstance();
|
final Localizer localizer = Localizer.getInstance();
|
||||||
@@ -360,10 +347,6 @@ public class Match {
|
|||||||
if (!removedAnteCards.isEmpty()) {
|
if (!removedAnteCards.isEmpty()) {
|
||||||
game.getAction().revealAnte(localizer.getMessage("lblAnteCardsRemoved"), removedAnteCards);
|
game.getAction().revealAnte(localizer.getMessage("lblAnteCardsRemoved"), removedAnteCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unsupported.isEmpty()) {
|
|
||||||
game.getAction().revealUnsupported(unsupported);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeAnte(Game lastGame) {
|
private void executeAnte(Game lastGame) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.game.ability;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.spellability.AbilityActivated;
|
import forge.game.spellability.AbilityActivated;
|
||||||
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -17,7 +18,14 @@ public class AbilityApiBased extends AbilityActivated {
|
|||||||
api = api0;
|
api = api0;
|
||||||
effect = api.getSpellEffect();
|
effect = api.getSpellEffect();
|
||||||
|
|
||||||
effect.buildSpellAbility(this);
|
if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) {
|
||||||
|
this.setManaPart(new AbilityManaPart(this, mapParams));
|
||||||
|
this.setUndoable(true); // will try at least
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) {
|
||||||
|
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -202,6 +202,15 @@ public final class AbilityFactory {
|
|||||||
final Card hostCard = state.getCard();
|
final Card hostCard = state.getCard();
|
||||||
TargetRestrictions abTgt = mapParams.containsKey("ValidTgts") ? readTarget(mapParams) : null;
|
TargetRestrictions abTgt = mapParams.containsKey("ValidTgts") ? readTarget(mapParams) : null;
|
||||||
|
|
||||||
|
if (api == ApiType.CopySpellAbility || api == ApiType.Counter || api == ApiType.ChangeTargets || api == ApiType.ControlSpell) {
|
||||||
|
// Since all "CopySpell" ABs copy things on the Stack no need for it to be everywhere
|
||||||
|
// Since all "Counter" or "ChangeTargets" abilities only target the Stack Zone
|
||||||
|
// No need to have each of those scripts have that info
|
||||||
|
if (abTgt != null) {
|
||||||
|
abTgt.setZone(ZoneType.Stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (abCost == null) {
|
if (abCost == null) {
|
||||||
abCost = parseAbilityCost(state, mapParams, type);
|
abCost = parseAbilityCost(state, mapParams, type);
|
||||||
}
|
}
|
||||||
@@ -239,10 +248,6 @@ public final class AbilityFactory {
|
|||||||
spellAbility.putParam("PrecostDesc", "Exhaust — ");
|
spellAbility.putParam("PrecostDesc", "Exhaust — ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapParams.containsKey("Named")) {
|
|
||||||
spellAbility.setName(mapParams.get("Named"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// *********************************************
|
// *********************************************
|
||||||
// set universal properties of the SpellAbility
|
// set universal properties of the SpellAbility
|
||||||
|
|
||||||
@@ -363,6 +368,9 @@ public final class AbilityFactory {
|
|||||||
if (mapParams.containsKey("TargetUnique")) {
|
if (mapParams.containsKey("TargetUnique")) {
|
||||||
abTgt.setUniqueTargets(true);
|
abTgt.setUniqueTargets(true);
|
||||||
}
|
}
|
||||||
|
if (mapParams.containsKey("TargetsFromSingleZone")) {
|
||||||
|
abTgt.setSingleZone(true);
|
||||||
|
}
|
||||||
if (mapParams.containsKey("TargetsWithoutSameCreatureType")) {
|
if (mapParams.containsKey("TargetsWithoutSameCreatureType")) {
|
||||||
abTgt.setWithoutSameCreatureType(true);
|
abTgt.setWithoutSameCreatureType(true);
|
||||||
}
|
}
|
||||||
@@ -384,9 +392,6 @@ public final class AbilityFactory {
|
|||||||
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
|
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
|
||||||
abTgt.setDifferentCMC(true);
|
abTgt.setDifferentCMC(true);
|
||||||
}
|
}
|
||||||
if (mapParams.containsKey("TargetsWithDifferentNames")) {
|
|
||||||
abTgt.setDifferentNames(true);
|
|
||||||
}
|
|
||||||
if (mapParams.containsKey("TargetsWithEqualToughness")) {
|
if (mapParams.containsKey("TargetsWithEqualToughness")) {
|
||||||
abTgt.setEqualToughness(true);
|
abTgt.setEqualToughness(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import java.util.Map.Entry;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
public class AbilityUtils {
|
public class AbilityUtils {
|
||||||
private final static ImmutableList<String> cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
|
private final static ImmutableList<String> cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
|
||||||
|
|
||||||
@@ -112,6 +113,13 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
} else if (defined.equals("Enchanted")) {
|
} else if (defined.equals("Enchanted")) {
|
||||||
c = hostCard.getEnchantingCard();
|
c = hostCard.getEnchantingCard();
|
||||||
|
if (c == null && sa instanceof SpellAbility) {
|
||||||
|
SpellAbility root = ((SpellAbility)sa).getRootAbility();
|
||||||
|
CardCollection sacrificed = root.getPaidList("Sacrificed", true);
|
||||||
|
if (sacrificed != null && !sacrificed.isEmpty()) {
|
||||||
|
c = sacrificed.getFirst().getEnchantingCard();
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (defined.equals("TopOfGraveyard")) {
|
} else if (defined.equals("TopOfGraveyard")) {
|
||||||
final CardCollectionView grave = player.getCardsIn(ZoneType.Graveyard);
|
final CardCollectionView grave = player.getCardsIn(ZoneType.Graveyard);
|
||||||
|
|
||||||
@@ -522,8 +530,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
} else if (calcX[0].equals("OriginalHost")) {
|
} else if (calcX[0].equals("OriginalHost")) {
|
||||||
val = xCount(ability.getOriginalHost(), calcX[1], ability);
|
val = xCount(ability.getOriginalHost(), calcX[1], ability);
|
||||||
} else if (calcX[0].equals("DungeonsCompleted")) {
|
|
||||||
val = handlePaid(player.getCompletedDungeons(), calcX[1], card, ability);
|
|
||||||
} else if (calcX[0].startsWith("ExiledWith")) {
|
} else if (calcX[0].startsWith("ExiledWith")) {
|
||||||
val = handlePaid(card.getExiledCards(), calcX[1], card, ability);
|
val = handlePaid(card.getExiledCards(), calcX[1], card, ability);
|
||||||
} else if (calcX[0].startsWith("Convoked")) {
|
} else if (calcX[0].startsWith("Convoked")) {
|
||||||
@@ -1871,14 +1877,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
return doXMath(v, expr, c, ctb);
|
return doXMath(v, expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count$FromNamedAbility[abilityName].<True>.<False>
|
|
||||||
if (sq[0].startsWith("FromNamedAbility")) {
|
|
||||||
String abilityNamed = sq[0].substring(16);
|
|
||||||
SpellAbility trigSA = sa.getHostCard().getCastSA();
|
|
||||||
boolean fromNamedAbility = trigSA != null && trigSA.getName().equals(abilityNamed);
|
|
||||||
return doXMath(calculateAmount(c, sq[fromNamedAbility ? 1 : 2], ctb), expr, c, ctb);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// fallback if ctb isn't a spellability
|
// fallback if ctb isn't a spellability
|
||||||
if (sq[0].startsWith("LastStateBattlefield")) {
|
if (sq[0].startsWith("LastStateBattlefield")) {
|
||||||
@@ -2347,9 +2345,6 @@ public class AbilityUtils {
|
|||||||
if (sq[0].equals("YourSpeed")) {
|
if (sq[0].equals("YourSpeed")) {
|
||||||
return doXMath(player.getSpeed(), expr, c, ctb);
|
return doXMath(player.getSpeed(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
if (sq[0].equals("AllFourBend")) {
|
|
||||||
return doXMath(calculateAmount(c, sq[player.hasAllElementBend() ? 1 : 2], ctb), expr, c, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sq[0].equals("Night")) {
|
if (sq[0].equals("Night")) {
|
||||||
return doXMath(calculateAmount(c, sq[game.isNight() ? 1 : 2], ctb), expr, c, ctb);
|
return doXMath(calculateAmount(c, sq[game.isNight() ? 1 : 2], ctb), expr, c, ctb);
|
||||||
@@ -2890,6 +2885,21 @@ public class AbilityUtils {
|
|||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sq[0].startsWith("DifferentCardNames_")) {
|
||||||
|
final List<String> crdname = Lists.newArrayList();
|
||||||
|
final String restriction = l[0].substring(19);
|
||||||
|
CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb);
|
||||||
|
// TODO rewrite with sharesName to respect Spy Kit
|
||||||
|
for (final Card card : list) {
|
||||||
|
String name = card.getName();
|
||||||
|
// CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common
|
||||||
|
if (!crdname.contains(name) && !name.isEmpty()) {
|
||||||
|
crdname.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(crdname.size(), expr, c, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("MostProminentCreatureType")) {
|
if (sq[0].startsWith("MostProminentCreatureType")) {
|
||||||
String restriction = l[0].split(" ")[1];
|
String restriction = l[0].split(" ")[1];
|
||||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||||
@@ -2904,6 +2914,13 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO move below to handlePaid
|
// TODO move below to handlePaid
|
||||||
|
if (sq[0].startsWith("SumPower")) {
|
||||||
|
final String[] restrictions = l[0].split("_");
|
||||||
|
int sumPower = game.getCardsIn(ZoneType.Battlefield).stream()
|
||||||
|
.filter(CardPredicates.restriction(restrictions[1], player, c, ctb))
|
||||||
|
.mapToInt(Card::getNetPower).sum();
|
||||||
|
return doXMath(sumPower, expr, c, ctb);
|
||||||
|
}
|
||||||
if (sq[0].startsWith("DifferentPower_")) {
|
if (sq[0].startsWith("DifferentPower_")) {
|
||||||
final String restriction = l[0].substring(15);
|
final String restriction = l[0].substring(15);
|
||||||
final int uniquePowers = (int) game.getCardsIn(ZoneType.Battlefield).stream()
|
final int uniquePowers = (int) game.getCardsIn(ZoneType.Battlefield).stream()
|
||||||
@@ -3423,7 +3440,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) {
|
public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) {
|
||||||
|
|
||||||
final String[] l = s.split("/");
|
final String[] l = s.split("/");
|
||||||
final String m = CardFactoryUtil.extractOperators(s);
|
final String m = CardFactoryUtil.extractOperators(s);
|
||||||
|
|
||||||
@@ -3610,10 +3626,46 @@ public class AbilityUtils {
|
|||||||
return doXMath(player.hasBeenDealtCombatDamageSinceLastTurn() ? 1 : 0, m, source, ctb);
|
return doXMath(player.hasBeenDealtCombatDamageSinceLastTurn() ? 1 : 0, m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.equals("DungeonsCompleted")) {
|
||||||
|
return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.equals("RingTemptedYou")) {
|
if (value.equals("RingTemptedYou")) {
|
||||||
return doXMath(player.getNumRingTemptedYou(), m, source, ctb);
|
return doXMath(player.getNumRingTemptedYou(), m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.startsWith("DungeonCompletedNamed")) {
|
||||||
|
String [] full = value.split("_");
|
||||||
|
String name = full[1];
|
||||||
|
int completed = 0;
|
||||||
|
List<Card> dungeons = player.getCompletedDungeons();
|
||||||
|
for (Card c : dungeons) {
|
||||||
|
if (c.getName().equals(name)) {
|
||||||
|
++completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(completed, m, source, ctb);
|
||||||
|
}
|
||||||
|
if (value.equals("DifferentlyNamedDungeonsCompleted")) {
|
||||||
|
int amount = 0;
|
||||||
|
List<Card> dungeons = player.getCompletedDungeons();
|
||||||
|
for (int i = 0; i < dungeons.size(); ++i) {
|
||||||
|
Card d1 = dungeons.get(i);
|
||||||
|
boolean hasSameName = false;
|
||||||
|
for (int j = i - 1; j >= 0; --j) {
|
||||||
|
Card d2 = dungeons.get(j);
|
||||||
|
if (d1.getName().equals(d2.getName())) {
|
||||||
|
hasSameName = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasSameName) {
|
||||||
|
++amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(amount, m, source, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.equals("AttractionsVisitedThisTurn")) {
|
if (value.equals("AttractionsVisitedThisTurn")) {
|
||||||
return doXMath(player.getAttractionsVisitedThisTurn(), m, source, ctb);
|
return doXMath(player.getAttractionsVisitedThisTurn(), m, source, ctb);
|
||||||
}
|
}
|
||||||
@@ -3692,6 +3744,10 @@ public class AbilityUtils {
|
|||||||
return CardLists.getTotalPower(paidList, ctb);
|
return CardLists.getTotalPower(paidList, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.startsWith("SumToughness")) {
|
||||||
|
return Aggregates.sum(paidList, Card::getNetToughness);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.startsWith("GreatestCMC")) {
|
if (string.startsWith("GreatestCMC")) {
|
||||||
return Aggregates.max(paidList, Card::getCMC);
|
return Aggregates.max(paidList, Card::getCMC);
|
||||||
}
|
}
|
||||||
@@ -3700,10 +3756,6 @@ public class AbilityUtils {
|
|||||||
return CardUtil.getColorsFromCards(paidList).countColors();
|
return CardUtil.getColorsFromCards(paidList).countColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.startsWith("DifferentCardNames")) {
|
|
||||||
return doXMath(CardLists.getDifferentNamesCount(paidList), CardFactoryUtil.extractOperators(string), source, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.equals("DifferentColorPair")) {
|
if (string.equals("DifferentColorPair")) {
|
||||||
final Set<ColorSet> diffPair = new HashSet<>();
|
final Set<ColorSet> diffPair = new HashSet<>();
|
||||||
for (final Card card : paidList) {
|
for (final Card card : paidList) {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ public enum ApiType {
|
|||||||
AddPhase (AddPhaseEffect.class),
|
AddPhase (AddPhaseEffect.class),
|
||||||
AddTurn (AddTurnEffect.class),
|
AddTurn (AddTurnEffect.class),
|
||||||
AdvanceCrank (AdvanceCrankEffect.class),
|
AdvanceCrank (AdvanceCrankEffect.class),
|
||||||
Airbend (AirbendEffect.class),
|
|
||||||
AlterAttribute (AlterAttributeEffect.class),
|
AlterAttribute (AlterAttributeEffect.class),
|
||||||
Amass (AmassEffect.class),
|
Amass (AmassEffect.class),
|
||||||
Animate (AnimateEffect.class),
|
Animate (AnimateEffect.class),
|
||||||
@@ -82,7 +81,6 @@ public enum ApiType {
|
|||||||
Draft (DraftEffect.class),
|
Draft (DraftEffect.class),
|
||||||
Draw (DrawEffect.class),
|
Draw (DrawEffect.class),
|
||||||
EachDamage (DamageEachEffect.class),
|
EachDamage (DamageEachEffect.class),
|
||||||
Earthbend (EarthbendEffect.class),
|
|
||||||
Effect (EffectEffect.class),
|
Effect (EffectEffect.class),
|
||||||
Encode (EncodeEffect.class),
|
Encode (EncodeEffect.class),
|
||||||
EndCombatPhase (EndCombatPhaseEffect.class),
|
EndCombatPhase (EndCombatPhaseEffect.class),
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ public abstract class SpellAbilityEffect {
|
|||||||
return sa.getDescription();
|
return sa.getDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildSpellAbility(final SpellAbility sa) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns this effect description with needed prelude and epilogue.
|
* Returns this effect description with needed prelude and epilogue.
|
||||||
* @param params
|
* @param params
|
||||||
@@ -1069,7 +1067,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
// if ability was granted use that source so they can be kept apart later
|
// if ability was granted use that source so they can be kept apart later
|
||||||
if (cause.isCopiedTrait()) {
|
if (cause.isCopiedTrait()) {
|
||||||
exilingSource = cause.getOriginalHost();
|
exilingSource = cause.getOriginalHost();
|
||||||
} else if (!cause.isSpell() && cause.getKeyword() != null && cause.getKeyword().getStatic() != null) {
|
} else if (cause.getKeyword() != null && cause.getKeyword().getStatic() != null) {
|
||||||
exilingSource = cause.getKeyword().getStatic().getOriginalHost();
|
exilingSource = cause.getKeyword().getStatic().getOriginalHost();
|
||||||
}
|
}
|
||||||
movedCard.setExiledWith(exilingSource);
|
movedCard.setExiledWith(exilingSource);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.Spell;
|
import forge.game.spellability.Spell;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
|
||||||
@@ -23,7 +24,13 @@ public class SpellApiBased extends Spell {
|
|||||||
// A spell is always intrinsic
|
// A spell is always intrinsic
|
||||||
this.setIntrinsic(true);
|
this.setIntrinsic(true);
|
||||||
|
|
||||||
effect.buildSpellAbility(this);
|
if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) {
|
||||||
|
this.setManaPart(new AbilityManaPart(this, mapParams));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) {
|
||||||
|
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package forge.game.ability;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import forge.game.ability.effects.ChangeZoneAllEffect;
|
||||||
|
import forge.game.ability.effects.ChangeZoneEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.spellability.AbilityStatic;
|
import forge.game.spellability.AbilityStatic;
|
||||||
@@ -18,7 +20,9 @@ public class StaticAbilityApiBased extends AbilityStatic {
|
|||||||
api = api0;
|
api = api0;
|
||||||
effect = api.getSpellEffect();
|
effect = api.getSpellEffect();
|
||||||
|
|
||||||
effect.buildSpellAbility(this);
|
if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) {
|
||||||
|
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
package forge.game.ability.effects;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
|
|
||||||
import forge.game.Game;
|
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
|
||||||
import forge.game.card.Card;
|
|
||||||
import forge.game.card.CardZoneTable;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.trigger.TriggerType;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.Lang;
|
|
||||||
|
|
||||||
public class AirbendEffect extends SpellAbilityEffect {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
|
||||||
final StringBuilder sb = new StringBuilder("Airbend ");
|
|
||||||
|
|
||||||
Iterable<Card> tgts;
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
tgts = getCardsfromTargets(sa);
|
|
||||||
} else { // otherwise add self to list and go from there
|
|
||||||
tgts = sa.knownDetermineDefined(sa.getParam("Defined"));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(sa.getParamOrDefault("DefinedDesc", Lang.joinHomogenous(tgts)));
|
|
||||||
sb.append(".");
|
|
||||||
if (Iterables.size(tgts) > 1) {
|
|
||||||
sb.append(" (Exile them. While each one is exiled, its owner may cast it for {2} rather than its mana cost.)");
|
|
||||||
} else {
|
|
||||||
sb.append(" (Exile it. While it’s exiled, its owner may cast it for {2} rather than its mana cost.)");
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resolve(SpellAbility sa) {
|
|
||||||
final Card hostCard = sa.getHostCard();
|
|
||||||
final Game game = hostCard.getGame();
|
|
||||||
final Player pl = sa.getActivatingPlayer();
|
|
||||||
|
|
||||||
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
|
|
||||||
|
|
||||||
for (Card c : getTargetCards(sa)) {
|
|
||||||
final Card gameCard = game.getCardState(c, null);
|
|
||||||
// gameCard is LKI in that case, the card is not in game anymore
|
|
||||||
// or the timestamp did change
|
|
||||||
// this should check Self too
|
|
||||||
if (gameCard == null || !c.equalsWithGameTimestamp(gameCard) || gameCard.isPhasedOut()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gameCard.canExiledBy(sa, true)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
handleExiledWith(gameCard, sa);
|
|
||||||
|
|
||||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
|
||||||
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
|
|
||||||
|
|
||||||
Card movedCard = game.getAction().exile(gameCard, sa, moveParams);
|
|
||||||
|
|
||||||
if (movedCard == null || !movedCard.isInZone(ZoneType.Exile)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Effect to cast for 2 from exile
|
|
||||||
Card eff = createEffect(sa, movedCard.getOwner(), "Airbend" + movedCard, hostCard.getImageKey());
|
|
||||||
eff.addRemembered(movedCard);
|
|
||||||
|
|
||||||
StringBuilder sbPlay = new StringBuilder();
|
|
||||||
sbPlay.append("Mode$ Continuous | MayPlay$ True | MayPlayAltManaCost$ 2 | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand");
|
|
||||||
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
|
||||||
eff.addStaticAbility(sbPlay.toString());
|
|
||||||
|
|
||||||
addForgetOnMovedTrigger(eff, "Exile");
|
|
||||||
addForgetOnCastTrigger(eff, "Card.IsRemembered");
|
|
||||||
|
|
||||||
game.getAction().moveToCommand(eff, sa);
|
|
||||||
}
|
|
||||||
triggerList.triggerChangesZoneAll(game, sa);
|
|
||||||
handleExiledWith(triggerList.allCards(), sa);
|
|
||||||
|
|
||||||
pl.triggerElementalBend(TriggerType.Airbend);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -46,9 +46,6 @@ public class AlterAttributeEffect extends SpellAbilityEffect {
|
|||||||
boolean altered = false;
|
boolean altered = false;
|
||||||
|
|
||||||
switch (attr.trim()) {
|
switch (attr.trim()) {
|
||||||
case "Harnessed":
|
|
||||||
altered = gameCard.setHarnessed(activate);
|
|
||||||
break;
|
|
||||||
case "Plotted":
|
case "Plotted":
|
||||||
altered = gameCard.setPlotted(activate);
|
altered = gameCard.setPlotted(activate);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardZoneTable;
|
import forge.game.card.CardZoneTable;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.card.token.TokenInfo;
|
import forge.game.card.token.TokenInfo;
|
||||||
import forge.game.event.GameEventCombatChanged;
|
import forge.game.event.GameEventCombatChanged;
|
||||||
import forge.game.event.GameEventTokenCreated;
|
import forge.game.event.GameEventTokenCreated;
|
||||||
@@ -85,7 +86,7 @@ public class AmassEffect extends TokenEffectBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> params = Maps.newHashMap();
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
params.put("CounterType", CounterEnumType.P1P1);
|
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
|
||||||
params.put("Amount", amount);
|
params.put("Amount", amount);
|
||||||
Card tgt = activator.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), false, params);
|
Card tgt = activator.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), false, params);
|
||||||
|
|
||||||
|
|||||||
@@ -90,16 +90,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
c.addPerpetual(p);
|
c.addPerpetual(p);
|
||||||
p.applyEffect(c);
|
p.applyEffect(c);
|
||||||
}
|
}
|
||||||
if (sa.hasParam("ManaCost")) {
|
|
||||||
final ManaCost manaCost = new ManaCost(new ManaCostParser(sa.getParam("ManaCost")));
|
|
||||||
if (perpetual) {
|
|
||||||
PerpetualManaCost p = new PerpetualManaCost(timestamp, manaCost);
|
|
||||||
c.addPerpetual(p);
|
|
||||||
p.applyEffect(c);
|
|
||||||
} else {
|
|
||||||
c.addChangedManaCost(manaCost, timestamp, (long) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!addType.isEmpty() || !removeType.isEmpty() || addAllCreatureTypes || !remove.isEmpty()) {
|
if (!addType.isEmpty() || !removeType.isEmpty() || addAllCreatureTypes || !remove.isEmpty()) {
|
||||||
if (perpetual) {
|
if (perpetual) {
|
||||||
@@ -138,7 +128,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
if (perpetual) {
|
if (perpetual) {
|
||||||
c.addPerpetual(new PerpetualColors(timestamp, colors, overwrite));
|
c.addPerpetual(new PerpetualColors(timestamp, colors, overwrite));
|
||||||
}
|
}
|
||||||
c.addColor(colors, !overwrite, timestamp, null);
|
c.addColor(colors, !overwrite, timestamp, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("LeaveBattlefield")) {
|
if (sa.hasParam("LeaveBattlefield")) {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import forge.game.spellability.SpellAbility;
|
|||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
import forge.game.zone.MagicStack;
|
import forge.game.zone.MagicStack;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
|
|
||||||
@@ -28,13 +27,6 @@ import forge.util.Localizer;
|
|||||||
*/
|
*/
|
||||||
public class ChangeTargetsEffect extends SpellAbilityEffect {
|
public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildSpellAbility(SpellAbility sa) {
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
sa.getTargetRestrictions().setZone(ZoneType.Stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility)
|
* @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import com.google.common.collect.Iterables;
|
|||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameActionUtil;
|
import forge.game.GameActionUtil;
|
||||||
import forge.game.GameEntityCounterTable;
|
import forge.game.GameEntityCounterTable;
|
||||||
import forge.game.ability.AbilityFactory;
|
|
||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
@@ -22,12 +21,6 @@ import forge.util.Localizer;
|
|||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
|
|
||||||
public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildSpellAbility(SpellAbility sa) {
|
|
||||||
AbilityFactory.adjustChangeZoneTarget(sa.getMapParams(), sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
// TODO build Stack Description will need expansion as more cards are added
|
// TODO build Stack Description will need expansion as more cards are added
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.google.common.collect.Maps;
|
|||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.game.*;
|
import forge.game.*;
|
||||||
import forge.game.ability.AbilityFactory;
|
|
||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
@@ -19,6 +18,7 @@ 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.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.Zone;
|
import forge.game.zone.Zone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -31,15 +31,9 @@ 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 {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildSpellAbility(SpellAbility sa) {
|
|
||||||
AbilityFactory.adjustChangeZoneTarget(sa.getMapParams(), sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
@@ -104,7 +98,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
final String destination = sa.getParam("Destination");
|
final String destination = sa.getParam("Destination");
|
||||||
|
|
||||||
final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa) : 1;
|
|
||||||
String type = "card";
|
String type = "card";
|
||||||
boolean defined = false;
|
boolean defined = false;
|
||||||
if (sa.hasParam("ChangeTypeDesc")) {
|
if (sa.hasParam("ChangeTypeDesc")) {
|
||||||
@@ -119,11 +112,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
type = Lang.joinHomogenous(tgts);
|
type = Lang.joinHomogenous(tgts);
|
||||||
defined = true;
|
defined = true;
|
||||||
} else if (sa.hasParam("ChangeType") && !sa.getParam("ChangeType").equals("Card")) {
|
} else if (sa.hasParam("ChangeType") && !sa.getParam("ChangeType").equals("Card")) {
|
||||||
List<String> typeList = Arrays.stream(sa.getParam("ChangeType").split(",")).map(ct -> CardType.isACardType(ct) ? ct.toLowerCase() : ct).collect(Collectors.toList());
|
final String ct = sa.getParam("ChangeType");
|
||||||
type = Lang.joinHomogenous(typeList, null, num == 1 ? "or" : "and/or");
|
type = CardType.CoreType.isValidEnum(ct) ? ct.toLowerCase() : ct;
|
||||||
}
|
}
|
||||||
final String cardTag = type.contains("card") ? "" : " card";
|
final String cardTag = type.contains("card") ? "" : " card";
|
||||||
|
|
||||||
|
final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa) : 1;
|
||||||
boolean tapped = sa.hasParam("Tapped");
|
boolean tapped = sa.hasParam("Tapped");
|
||||||
boolean attacking = sa.hasParam("Attacking");
|
boolean attacking = sa.hasParam("Attacking");
|
||||||
if (sa.isNinjutsu()) {
|
if (sa.isNinjutsu()) {
|
||||||
@@ -153,9 +147,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
} else {
|
} else {
|
||||||
sb.append(" for ");
|
sb.append(" for ");
|
||||||
}
|
}
|
||||||
if (num != 1) {
|
|
||||||
sb.append(" up to ");
|
|
||||||
}
|
|
||||||
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
|
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
|
||||||
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination) != null && ZoneType.smartValueOf(destination).isHidden()) {
|
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination) != null && ZoneType.smartValueOf(destination).isHidden()) {
|
||||||
if (choosers.size() == 1) {
|
if (choosers.size() == 1) {
|
||||||
@@ -768,7 +759,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
StringBuilder sbPlay = new StringBuilder();
|
StringBuilder sbPlay = new StringBuilder();
|
||||||
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand+!ThisTurnEntered");
|
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand+!ThisTurnEntered");
|
||||||
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
||||||
eff.addStaticAbility(sbPlay.toString());
|
final StaticAbility st = eff.addStaticAbility(sbPlay.toString());
|
||||||
eff.addRemembered(movedCard);
|
eff.addRemembered(movedCard);
|
||||||
addForgetOnMovedTrigger(eff, "Exile");
|
addForgetOnMovedTrigger(eff, "Exile");
|
||||||
addForgetOnCastTrigger(eff, "Card.IsRemembered");
|
addForgetOnCastTrigger(eff, "Card.IsRemembered");
|
||||||
@@ -932,7 +923,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
List<ZoneType> origin = Lists.newArrayList();
|
List<ZoneType> origin = Lists.newArrayList();
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
|
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||||
}
|
}
|
||||||
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
|
|
||||||
@@ -973,10 +964,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
String prompt;
|
String prompt;
|
||||||
if (sa.hasParam("OptionalPrompt")) {
|
if (sa.hasParam("OptionalPrompt")) {
|
||||||
prompt = sa.getParam("OptionalPrompt");
|
prompt = sa.getParam("OptionalPrompt");
|
||||||
} else if (defined) {
|
|
||||||
prompt = Localizer.getInstance().getMessage("lblPutThatCardFromPlayerOriginToDestination", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase(), destination.getTranslatedName().toLowerCase());
|
|
||||||
} else {
|
} else {
|
||||||
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
|
if (defined) {
|
||||||
|
prompt = Localizer.getInstance().getMessage("lblPutThatCardFromPlayerOriginToDestination", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase(), destination.getTranslatedName().toLowerCase());
|
||||||
|
} else {
|
||||||
|
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
String message = MessageUtil.formatMessage(prompt , decider, player);
|
String message = MessageUtil.formatMessage(prompt , decider, player);
|
||||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
|
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
|
||||||
@@ -1478,7 +1471,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
if (ZoneType.Exile.equals(destination) && sa.hasParam("WithCountersType")) {
|
if (ZoneType.Exile.equals(destination) && sa.hasParam("WithCountersType")) {
|
||||||
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
||||||
int cAmount = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
int cAmount = AbilityUtils.calculateAmount(sa.getOriginalHost(), sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
movedCard.addCounter(cType, cAmount, player, table);
|
movedCard.addCounter(cType, cAmount, player, table);
|
||||||
table.replaceCounterEffect(game, sa, true);
|
table.replaceCounterEffect(game, sa, true);
|
||||||
|
|||||||
@@ -10,17 +10,8 @@ import forge.game.card.Card;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
|
|
||||||
public class ControlSpellEffect extends SpellAbilityEffect {
|
public class ControlSpellEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildSpellAbility(SpellAbility sa) {
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
sa.getTargetRestrictions().setZone(ZoneType.Stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -287,17 +287,22 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
|||||||
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
||||||
// need to create a physical card first, i need the original card faces
|
// need to create a physical card first, i need the original card faces
|
||||||
copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame());
|
copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame());
|
||||||
|
|
||||||
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
|
|
||||||
// force update the now set State
|
|
||||||
if (original.isTransformable()) {
|
if (original.isTransformable()) {
|
||||||
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
|
||||||
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
||||||
// the resulting token is a transforming token that has both a front face and a back face.
|
// the resulting token is a transforming token that has both a front face and a back face.
|
||||||
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
||||||
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
||||||
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
||||||
copy.setBackSide(original.isBackSide());
|
copy.setBackSide(original.isBackSide());
|
||||||
|
if (original.isTransformed()) {
|
||||||
|
copy.incrementTransformedTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
|
||||||
|
// force update the now set State
|
||||||
|
if (original.isTransformable()) {
|
||||||
|
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
||||||
} else {
|
} else {
|
||||||
copy.setState(copy.getCurrentStateName(), true, true);
|
copy.setState(copy.getCurrentStateName(), true, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,6 @@ import java.util.Map;
|
|||||||
|
|
||||||
|
|
||||||
public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||||
@Override
|
|
||||||
public void buildSpellAbility(SpellAbility sa) {
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
sa.getTargetRestrictions().setZone(ZoneType.Stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
|
|||||||
@@ -21,13 +21,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class CounterEffect extends SpellAbilityEffect {
|
public class CounterEffect extends SpellAbilityEffect {
|
||||||
@Override
|
|
||||||
public void buildSpellAbility(SpellAbility sa) {
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
sa.getTargetRestrictions().setZone(ZoneType.Stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@@ -102,29 +102,24 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
int totalRemoved = 0;
|
int totalRemoved = 0;
|
||||||
CardCollectionView srcCards;
|
CardCollectionView srcCards;
|
||||||
|
|
||||||
if (sa.hasParam("Choices")) {
|
if (sa.hasParam("Choices")) {
|
||||||
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
||||||
: ZoneType.Battlefield;
|
: ZoneType.Battlefield;
|
||||||
srcCards = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
|
||||||
activator, source, sa);
|
|
||||||
} else {
|
|
||||||
srcCards = getTargetCards(sa);
|
|
||||||
}
|
|
||||||
if (sa.isReplacementAbility()) {
|
|
||||||
srcCards = new CardCollection(srcCards).filter(c -> !c.isInPlay() || sa.getLastStateBattlefield().contains(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.hasParam("Choices")) {
|
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
||||||
|
activator, source, sa);
|
||||||
|
|
||||||
int min = 1;
|
int min = 1;
|
||||||
int max = 1;
|
int max = 1;
|
||||||
if (sa.hasParam("ChoiceOptional")) {
|
if (sa.hasParam("ChoiceOptional")) {
|
||||||
min = 0;
|
min = 0;
|
||||||
max = srcCards.size();
|
max = choices.size();
|
||||||
}
|
}
|
||||||
if (sa.hasParam("ChoiceNum")) {
|
if (sa.hasParam("ChoiceNum")) {
|
||||||
min = max = AbilityUtils.calculateAmount(source, sa.getParam("ChoiceNum"), sa);
|
min = max = AbilityUtils.calculateAmount(source, sa.getParam("ChoiceNum"), sa);
|
||||||
}
|
}
|
||||||
if (srcCards.size() < min) {
|
if (choices.size() < min) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +128,13 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
title = title.replace(" ", " ");
|
title = title.replace(" ", " ");
|
||||||
Map<String, Object> params = Maps.newHashMap();
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
params.put("CounterType", counterType);
|
params.put("CounterType", counterType);
|
||||||
srcCards = pc.chooseCardsForEffect(srcCards, sa, title, min, max, min == 0, params);
|
srcCards = pc.chooseCardsForEffect(choices, sa, title, min, max, min == 0, params);
|
||||||
} else {
|
} else {
|
||||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||||
if (!tgtPlayer.isInGame()) {
|
if (!tgtPlayer.isInGame()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Removing energy
|
||||||
if (type.equals("All")) {
|
if (type.equals("All")) {
|
||||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
||||||
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||||
@@ -154,6 +150,8 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srcCards = getTargetCards(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Card tgtCard : srcCards) {
|
for (final Card tgtCard : srcCards) {
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ public class DamageResolveEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
CardDamageMap damageMap = sa.getDamageMap();
|
CardDamageMap damageMap = sa.getDamageMap();
|
||||||
if (damageMap == null) {
|
|
||||||
// this can happen if damagesource was missing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CardDamageMap preventMap = sa.getPreventMap();
|
CardDamageMap preventMap = sa.getPreventMap();
|
||||||
GameEntityCounterTable counterTable = sa.getCounterTable();
|
GameEntityCounterTable counterTable = sa.getCounterTable();
|
||||||
|
|
||||||
|
|||||||
@@ -73,11 +73,6 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||||
if (zone.equals(ZoneType.Battlefield)) {
|
|
||||||
if (sa.hasParam("Tapped")) {
|
|
||||||
made.setTapped(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (zone.equals(ZoneType.Exile)) {
|
if (zone.equals(ZoneType.Exile)) {
|
||||||
handleExiledWith(made, sa);
|
handleExiledWith(made, sa);
|
||||||
if (sa.hasParam("ExileFaceDown")) {
|
if (sa.hasParam("ExileFaceDown")) {
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
package forge.game.ability.effects;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
|
|
||||||
import forge.card.RemoveType;
|
|
||||||
import forge.game.Game;
|
|
||||||
import forge.game.GameEntityCounterTable;
|
|
||||||
import forge.game.ability.AbilityFactory;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
|
||||||
import forge.game.card.Card;
|
|
||||||
import forge.game.card.CardCopyService;
|
|
||||||
import forge.game.card.CounterEnumType;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.trigger.Trigger;
|
|
||||||
import forge.game.trigger.TriggerHandler;
|
|
||||||
import forge.game.trigger.TriggerType;
|
|
||||||
import forge.util.Lang;
|
|
||||||
|
|
||||||
public class EarthbendEffect extends SpellAbilityEffect {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
|
||||||
final StringBuilder sb = new StringBuilder("Earthbend ");
|
|
||||||
final Card card = sa.getHostCard();
|
|
||||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
|
|
||||||
|
|
||||||
sb.append(amount).append(". (Target land you control becomes a 0/0 creature with haste that's still a land. Put ");
|
|
||||||
sb.append(Lang.nounWithNumeral(amount, "+1/+1 counter"));
|
|
||||||
sb.append(" on it. When it dies or is exiled, return it to the battlefield tapped.)");
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildSpellAbility(final SpellAbility sa) {
|
|
||||||
TargetRestrictions abTgt = new TargetRestrictions("Select target land you control", "Land.YouCtrl".split(","), "1", "1");
|
|
||||||
sa.setTargetRestrictions(abTgt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resolve(SpellAbility sa) {
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final Game game = source.getGame();
|
|
||||||
final Player pl = sa.getActivatingPlayer();
|
|
||||||
int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("Num", "1"), sa);
|
|
||||||
|
|
||||||
long ts = game.getNextTimestamp();
|
|
||||||
|
|
||||||
String desc = "When it dies or is exiled, return it to the battlefield tapped.";
|
|
||||||
String sbTrigA = "Mode$ ChangesZone | ValidCard$ Card.IsTriggerRemembered | Origin$ Battlefield | Destination$ Graveyard | TriggerDescription$ " + desc;
|
|
||||||
String sbTrigB = "Mode$ Exiled | Origin$ Battlefield | ValidCard$ Card.IsTriggerRemembered | TriggerZones$ Battlefield | TriggerDescription$ " + desc;
|
|
||||||
|
|
||||||
// Earthbend should only target one land
|
|
||||||
for (Card c : getTargetCards(sa)) {
|
|
||||||
c.addNewPT(0, 0, ts, 0);
|
|
||||||
c.addChangedCardTypes(Arrays.asList("Creature"), null, false, EnumSet.noneOf(RemoveType.class), ts, 0, true, false);
|
|
||||||
c.addChangedCardKeywords(Arrays.asList("Haste"), null, false, ts, null);
|
|
||||||
|
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
|
||||||
c.addCounter(CounterEnumType.P1P1, num, pl, table);
|
|
||||||
table.replaceCounterEffect(game, sa, true);
|
|
||||||
|
|
||||||
buildTrigger(sa, c, sbTrigA, "Graveyard");
|
|
||||||
buildTrigger(sa, c, sbTrigB, "Exile");
|
|
||||||
}
|
|
||||||
pl.triggerElementalBend(TriggerType.Earthbend);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void buildTrigger(SpellAbility sa, Card c, String sbTrig, String zone) {
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final Game game = source.getGame();
|
|
||||||
String trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$ " + zone + " | Destination$ Battlefield | Tapped$ True";
|
|
||||||
|
|
||||||
final Trigger trig = TriggerHandler.parseTrigger(sbTrig, CardCopyService.getLKICopy(source), sa.isIntrinsic());
|
|
||||||
final SpellAbility newSa = AbilityFactory.getAbility(trigSA, sa.getHostCard());
|
|
||||||
newSa.setIntrinsic(sa.isIntrinsic());
|
|
||||||
trig.addRemembered(c);
|
|
||||||
trig.setOverridingAbility(newSa);
|
|
||||||
trig.setSpawningAbility(sa.copy(sa.getHostCard(), true));
|
|
||||||
trig.setKeyword(trig.getSpawningAbility().getKeyword());
|
|
||||||
|
|
||||||
game.getTriggerHandler().registerDelayedTrigger(trig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import java.util.Map;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.ImageKeys;
|
import forge.ImageKeys;
|
||||||
import forge.StaticData;
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
@@ -143,20 +142,15 @@ public class EffectEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String image;
|
String image;
|
||||||
if (name.startsWith("Emblem")) {
|
if (sa.hasParam("Image")) {
|
||||||
if (sa.hasParam("Image")) {
|
|
||||||
image = StaticData.instance().getOtherImageKey(sa.getParam("Image"), hostCard.getSetCode());
|
|
||||||
} else {
|
|
||||||
// try to get the image from name
|
|
||||||
String imageKey = TextUtil.fastReplace(
|
|
||||||
TextUtil.fastReplace(
|
|
||||||
TextUtil.fastReplace(name.toLowerCase(), " — ", "_"),
|
|
||||||
",", ""),
|
|
||||||
" ", "_");
|
|
||||||
image = StaticData.instance().getOtherImageKey(imageKey, hostCard.getSetCode());
|
|
||||||
}
|
|
||||||
} else if (sa.hasParam("Image")) {
|
|
||||||
image = ImageKeys.getTokenKey(sa.getParam("Image"));
|
image = ImageKeys.getTokenKey(sa.getParam("Image"));
|
||||||
|
} else if (name.startsWith("Emblem")) { // try to get the image from name
|
||||||
|
image = ImageKeys.getTokenKey(
|
||||||
|
TextUtil.fastReplace(
|
||||||
|
TextUtil.fastReplace(
|
||||||
|
TextUtil.fastReplace(name.toLowerCase(), " — ", "_"),
|
||||||
|
",", ""),
|
||||||
|
" ", "_").toLowerCase());
|
||||||
} else { // use host image
|
} else { // use host image
|
||||||
image = hostCard.getImageKey();
|
image = hostCard.getImageKey();
|
||||||
}
|
}
|
||||||
@@ -269,22 +263,22 @@ public class EffectEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Color(s)
|
||||||
if (hostCard.hasChosenColor()) {
|
if (hostCard.hasChosenColor()) {
|
||||||
eff.setChosenColors(Lists.newArrayList(hostCard.getChosenColors()));
|
eff.setChosenColors(Lists.newArrayList(hostCard.getChosenColors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Cards
|
||||||
if (hostCard.hasChosenCard()) {
|
if (hostCard.hasChosenCard()) {
|
||||||
eff.setChosenCards(hostCard.getChosenCards());
|
eff.setChosenCards(hostCard.getChosenCards());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Player
|
||||||
if (hostCard.hasChosenPlayer()) {
|
if (hostCard.hasChosenPlayer()) {
|
||||||
eff.setChosenPlayer(hostCard.getChosenPlayer());
|
eff.setChosenPlayer(hostCard.getChosenPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hostCard.getChosenDirection() != null) {
|
// Set Chosen Type
|
||||||
eff.setChosenDirection(hostCard.getChosenDirection());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hostCard.hasChosenType()) {
|
if (hostCard.hasChosenType()) {
|
||||||
eff.setChosenType(hostCard.getChosenType());
|
eff.setChosenType(hostCard.getChosenType());
|
||||||
}
|
}
|
||||||
@@ -292,10 +286,12 @@ public class EffectEffect extends SpellAbilityEffect {
|
|||||||
eff.setChosenType2(hostCard.getChosenType2());
|
eff.setChosenType2(hostCard.getChosenType2());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen name
|
||||||
if (hostCard.hasNamedCard()) {
|
if (hostCard.hasNamedCard()) {
|
||||||
eff.setNamedCards(Lists.newArrayList(hostCard.getNamedCards()));
|
eff.setNamedCards(Lists.newArrayList(hostCard.getNamedCards()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chosen number
|
||||||
if (sa.hasParam("SetChosenNumber")) {
|
if (sa.hasParam("SetChosenNumber")) {
|
||||||
eff.setChosenNumber(AbilityUtils.calculateAmount(hostCard, sa.getParam("SetChosenNumber"), sa));
|
eff.setChosenNumber(AbilityUtils.calculateAmount(hostCard, sa.getParam("SetChosenNumber"), sa));
|
||||||
} else if (hostCard.hasChosenNumber()) {
|
} else if (hostCard.hasChosenNumber()) {
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
|
|||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Game game = host.getGame();
|
final Game game = host.getGame();
|
||||||
|
|
||||||
// CR 603.12a if the trigger event or events occur multiple times during the resolution of the spell or ability that created it,
|
|
||||||
// the reflexive triggered ability will trigger once for each of those times
|
|
||||||
int amt = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TriggerAmount", "1"), sa);
|
int amt = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TriggerAmount", "1"), sa);
|
||||||
if (amt <= 0) {
|
if (amt <= 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -54,29 +54,33 @@ public class LifeExchangeEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
final int life1 = p1.getLife();
|
final int life1 = p1.getLife();
|
||||||
final int life2 = p2.getLife();
|
final int life2 = p2.getLife();
|
||||||
final int diff = Math.abs(life1 - life2);
|
|
||||||
|
|
||||||
if (life2 > life1) {
|
if (sa.hasParam("RememberDifference")) {
|
||||||
// swap players
|
final int diff = life1 - life2;
|
||||||
Player tmp = p2;
|
source.addRemembered(diff);
|
||||||
p2 = p1;
|
|
||||||
p1 = tmp;
|
|
||||||
}
|
}
|
||||||
if (diff > 0 && p1.canLoseLife() && p2.canGainLife()) {
|
|
||||||
|
final Map<Player, Integer> lossMap = Maps.newHashMap();
|
||||||
|
if ((life1 > life2) && p1.canLoseLife() && p2.canGainLife()) {
|
||||||
|
final int diff = life1 - life2;
|
||||||
final int lost = p1.loseLife(diff, false, false);
|
final int lost = p1.loseLife(diff, false, false);
|
||||||
p2.gainLife(diff, source, sa);
|
p2.gainLife(diff, source, sa);
|
||||||
if (lost > 0) {
|
if (lost > 0) {
|
||||||
final Map<Player, Integer> lossMap = Maps.newHashMap();
|
|
||||||
lossMap.put(p1, lost);
|
lossMap.put(p1, lost);
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
|
|
||||||
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
|
|
||||||
if (sa.hasParam("RememberOwnLoss") && p1.equals(sa.getActivatingPlayer())) {
|
|
||||||
source.addRemembered(lost);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if ((life2 > life1) && p2.canLoseLife() && p1.canGainLife()) {
|
||||||
|
final int diff = life2 - life1;
|
||||||
|
final int lost = p2.loseLife(diff, false, false);
|
||||||
|
p1.gainLife(diff, source, sa);
|
||||||
|
if (lost > 0) {
|
||||||
|
lossMap.put(p2, lost);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// they are equal or can't be exchanged, so nothing to do
|
||||||
}
|
}
|
||||||
if (sa.hasParam("RememberDifference")) {
|
if (!lossMap.isEmpty()) { // Run triggers if any player actually lost life
|
||||||
source.addRemembered(p1.getLife() - p2.getLife());
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
|
||||||
|
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user