mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 09:48:02 +00:00
Compare commits
346 Commits
reduce-bit
...
cardTraitC
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1efddc9a92 | ||
|
|
01d5bf3c21 | ||
|
|
29a630386f | ||
|
|
75c7938f1e | ||
|
|
110e885c67 | ||
|
|
548b448f0d | ||
|
|
a2e80ac0f3 | ||
|
|
238d426202 | ||
|
|
d6f585a80c | ||
|
|
223aeb1bff | ||
|
|
4bf02ea61f | ||
|
|
ea186ae4af | ||
|
|
3e4cd9e189 | ||
|
|
e35c193f92 | ||
|
|
e5443fc394 | ||
|
|
9998092c70 | ||
|
|
9f81e0cd34 | ||
|
|
5569f18053 | ||
|
|
40190b5442 | ||
|
|
a33905241d | ||
|
|
3a40b560f6 | ||
|
|
d79fb96770 | ||
|
|
f50fe2f89c | ||
|
|
a36deab334 | ||
|
|
28feff99d7 | ||
|
|
bab8791012 | ||
|
|
db20b27c9d | ||
|
|
087db3f757 | ||
|
|
db194fab97 | ||
|
|
0585ece2c1 | ||
|
|
1611559909 | ||
|
|
d6dee9575b | ||
|
|
6dd9a731fa | ||
|
|
cf5a8508d6 | ||
|
|
0fa7df090e | ||
|
|
86838d94f7 | ||
|
|
83c4db07ac | ||
|
|
b544f2dd00 | ||
|
|
079e6f04ac | ||
|
|
d04c541578 | ||
|
|
7cc2cf530d | ||
|
|
61a2c7cadb | ||
|
|
efbf2e1a9c | ||
|
|
9b8441d45b | ||
|
|
f8b7a0fb9a | ||
|
|
ab49e97797 | ||
|
|
c27a9d136f | ||
|
|
91241ee53c | ||
|
|
31e537f42b | ||
|
|
e550c307c2 | ||
|
|
b4f01b7ebb | ||
|
|
7744474f39 | ||
|
|
1365b82968 | ||
|
|
5bb532bf9d | ||
|
|
258b8c18a9 | ||
|
|
a66349d8a1 | ||
|
|
1933cf1863 | ||
|
|
0d342c778f | ||
|
|
f452b94cb8 | ||
|
|
9325794e2f | ||
|
|
192a64bbc3 | ||
|
|
deb8369f11 | ||
|
|
b24f536190 | ||
|
|
28ec24069c | ||
|
|
f27472d9bd | ||
|
|
780cc8ddbf | ||
|
|
23555b9564 | ||
|
|
431827be35 | ||
|
|
b0dba74c6c | ||
|
|
ad0a690764 | ||
|
|
977f2c75b9 | ||
|
|
8313414f78 | ||
|
|
89955bf201 | ||
|
|
99d191901a | ||
|
|
1a306b3da3 | ||
|
|
9ff03cbd41 | ||
|
|
07a1dbc099 | ||
|
|
f24b3ea3b3 | ||
|
|
770cc72fcd | ||
|
|
8426a74900 | ||
|
|
8d98eda18d | ||
|
|
6e6509eaff | ||
|
|
c56ddee47d | ||
|
|
07985ac487 | ||
|
|
6c93491d7f | ||
|
|
c9b012c88a | ||
|
|
9b53144976 | ||
|
|
31020296d8 | ||
|
|
65fb3414d8 | ||
|
|
98fee0d86b | ||
|
|
becdadb279 | ||
|
|
26da0ab0d4 | ||
|
|
e1f4d755e0 | ||
|
|
efe7d67d9f | ||
|
|
3a5e11504a | ||
|
|
6657602b80 | ||
|
|
eeca33855d | ||
|
|
cdc63f35bb | ||
|
|
5a9ea8d260 | ||
|
|
5a363922bd | ||
|
|
1ff16ca509 | ||
|
|
c878401197 | ||
|
|
8f518b7b1f | ||
|
|
fb624458f0 | ||
|
|
ee3220f33b | ||
|
|
7bc591fa78 | ||
|
|
c02d942fc3 | ||
|
|
3b9ded8270 | ||
|
|
fdf0c13a7f | ||
|
|
80b1cac394 | ||
|
|
8c80c32113 | ||
|
|
a74b033c26 | ||
|
|
16fcbc0ebf | ||
|
|
62ffada6fe | ||
|
|
e5a0d335af | ||
|
|
ba8a30ebdd | ||
|
|
e25dbe5196 | ||
|
|
3533ad8b74 | ||
|
|
2d20ce07e3 | ||
|
|
e5e0a9240d | ||
|
|
5e83fff859 | ||
|
|
2d0acc734e | ||
|
|
b6bc3a6e96 | ||
|
|
7ec7a65f33 | ||
|
|
2a97b67f72 | ||
|
|
06be01d81c | ||
|
|
386f6aaac6 | ||
|
|
82d11bfb45 | ||
|
|
f39d900fbf | ||
|
|
335edec357 | ||
|
|
e58f035b19 | ||
|
|
f85d610393 | ||
|
|
fce0095af1 | ||
|
|
82bea111df | ||
|
|
0aded8f1f4 | ||
|
|
84130f0586 | ||
|
|
ed4d1059dd | ||
|
|
c73be86211 | ||
|
|
6cb2e7a91a | ||
|
|
58713e65ed | ||
|
|
676c26fae7 | ||
|
|
f2e4e67021 | ||
|
|
18ba476fc9 | ||
|
|
d9ed82972f | ||
|
|
c9affaa1a1 | ||
|
|
8ef89adaef | ||
|
|
f25db898ae | ||
|
|
cddc41b353 | ||
|
|
d4e918660b | ||
|
|
3f16a3e27f | ||
|
|
53d27f1437 | ||
|
|
408dd310d3 | ||
|
|
0050446ff0 | ||
|
|
efb47d949f | ||
|
|
a67303866e | ||
|
|
756e80595c | ||
|
|
089a021d02 | ||
|
|
d89a94dcd2 | ||
|
|
b298f66348 | ||
|
|
fa277c7b7c | ||
|
|
76ec449d33 | ||
|
|
d481ade524 | ||
|
|
1b468efff1 | ||
|
|
9be9a4795b | ||
|
|
49bcf7ded4 | ||
|
|
28989b9c49 | ||
|
|
9e40b1f6cf | ||
|
|
a9e95caa42 | ||
|
|
eb39c97661 | ||
|
|
52f19272d0 | ||
|
|
4cf37379c7 | ||
|
|
746d0476c9 | ||
|
|
cf93d61d0c | ||
|
|
5d9197446b | ||
|
|
753d5560d6 | ||
|
|
1b68d30ff1 | ||
|
|
278eed7af9 | ||
|
|
6eb4e32225 | ||
|
|
e160a7d517 | ||
|
|
476cdda0c2 | ||
|
|
efdabb8bf6 | ||
|
|
ca648df852 | ||
|
|
04afb61351 | ||
|
|
869632361c | ||
|
|
d994abb559 | ||
|
|
d082c2c250 | ||
|
|
586a474168 | ||
|
|
b7268dd6fd | ||
|
|
c3e4ea228a | ||
|
|
237946f569 | ||
|
|
cae32e5b2a | ||
|
|
715ba9803f | ||
|
|
283198f0a4 | ||
|
|
bab889c406 | ||
|
|
92c6f5369d | ||
|
|
6042948aed | ||
|
|
136577fec0 | ||
|
|
180fda53df | ||
|
|
ca6b175fb8 | ||
|
|
dd25dc4b06 | ||
|
|
9893fc3cf9 | ||
|
|
67b6c2c03f | ||
|
|
bb073b8682 | ||
|
|
9bbfad5be3 | ||
|
|
2c7e7d5b63 | ||
|
|
94e7258a03 | ||
|
|
95eb90eac2 | ||
|
|
0f0a4c54e7 | ||
|
|
399923bfa4 | ||
|
|
39f0ab9eae | ||
|
|
8b1cd54417 | ||
|
|
ff2192fa7a | ||
|
|
08443a307c | ||
|
|
bd90e1bccc | ||
|
|
9cb0bd301e | ||
|
|
0923960215 | ||
|
|
0f68dc1ab6 | ||
|
|
09b88b8575 | ||
|
|
fae2f25b69 | ||
|
|
d3e0696ecc | ||
|
|
6a7723eba9 | ||
|
|
10d6fd157e | ||
|
|
048133df30 | ||
|
|
2fe7fec14f | ||
|
|
73e7b27c09 | ||
|
|
2fdbd5a85c | ||
|
|
78e27e0073 | ||
|
|
bad585c8c9 | ||
|
|
11d10a8129 | ||
|
|
0b89e9d137 | ||
|
|
70df1ff0aa | ||
|
|
24d3169592 | ||
|
|
b8db0fea5e | ||
|
|
514519b45b | ||
|
|
70f6bcb63c | ||
|
|
5a3e55f704 | ||
|
|
4452e07443 | ||
|
|
a125e6c0ea | ||
|
|
fa4688e113 | ||
|
|
0236609558 | ||
|
|
3f15fb1b98 | ||
|
|
e22718c72a | ||
|
|
e94985576c | ||
|
|
71e132a9e0 | ||
|
|
8d5d56bed8 | ||
|
|
386c9799c2 | ||
|
|
a1be657460 | ||
|
|
d4994e8fa0 | ||
|
|
fb054d9f64 | ||
|
|
8790843b3b | ||
|
|
38383dff02 | ||
|
|
a0f9923c21 | ||
|
|
812c0ac5b1 | ||
|
|
2e09146f44 | ||
|
|
25080411d5 | ||
|
|
55e20e96f9 | ||
|
|
981ab1c6ed | ||
|
|
a1089f8073 | ||
|
|
fe747a3908 | ||
|
|
a16c9480e7 | ||
|
|
b40e9fc817 | ||
|
|
6eec0e8988 | ||
|
|
d6320caadf | ||
|
|
c4f125525a | ||
|
|
56209261d0 | ||
|
|
5df4d64345 | ||
|
|
d6d6065104 | ||
|
|
333aea0641 | ||
|
|
c7bb3d49a6 | ||
|
|
7c982498dd | ||
|
|
0e2c95afed | ||
|
|
df6eb23341 | ||
|
|
1204de13c3 | ||
|
|
9cb21de11d | ||
|
|
2f16b9e110 | ||
|
|
cf28139b02 | ||
|
|
87e8e7d5e5 | ||
|
|
dcc3a681d9 | ||
|
|
cc7f30da88 | ||
|
|
ed9eabed38 | ||
|
|
99ac95bfca | ||
|
|
eb178fc9d1 | ||
|
|
7567e29cf1 | ||
|
|
b2a456140c | ||
|
|
9c3ff6b570 | ||
|
|
e4d238ba6c | ||
|
|
42363204dd | ||
|
|
de89d557dd | ||
|
|
c5a53b21e7 | ||
|
|
02f43b1ef0 | ||
|
|
d7cb0b7ac1 | ||
|
|
2efeb573e6 | ||
|
|
370ff638ab | ||
|
|
108564efe3 | ||
|
|
43f96657e0 | ||
|
|
cb004bfba2 | ||
|
|
f276597a82 | ||
|
|
4fd9855daa | ||
|
|
ebdce74f92 | ||
|
|
98e9b8f28b | ||
|
|
a31c0358a4 | ||
|
|
8640522fff | ||
|
|
a5fa350e7e | ||
|
|
c4a0cff1ca | ||
|
|
30d02ebbb6 | ||
|
|
9a90359283 | ||
|
|
edbcab544e | ||
|
|
f55bf4691d | ||
|
|
16e44309a6 | ||
|
|
c4d58e3dba | ||
|
|
16155d3670 | ||
|
|
bd929bcc72 | ||
|
|
e2cc52fd02 | ||
|
|
be5c7cfd04 | ||
|
|
9d14949138 | ||
|
|
6fe5dbad3c | ||
|
|
f4f8ee9cb6 | ||
|
|
0767eb03e0 | ||
|
|
feb6062f2f | ||
|
|
113dc4d5e8 | ||
|
|
ca20d98a2c | ||
|
|
599a629068 | ||
|
|
4c05ad655d | ||
|
|
203233f0d2 | ||
|
|
ac8f7357db | ||
|
|
4c396065f5 | ||
|
|
cc4dac38fe | ||
|
|
394818f533 | ||
|
|
bfd4a68e23 | ||
|
|
9d54e82214 | ||
|
|
18c2278066 | ||
|
|
b3d549a712 | ||
|
|
0887e78e90 | ||
|
|
df3f86dd73 | ||
|
|
89af1d0cf1 | ||
|
|
10a68d63a1 | ||
|
|
8ccebbc13c | ||
|
|
4c663cd943 | ||
|
|
ca16bf74f5 | ||
|
|
a1968ef9fd | ||
|
|
de1a999611 | ||
|
|
d0e8bc5de0 | ||
|
|
38fb647cd7 | ||
|
|
90bb83c5d4 | ||
|
|
467fff3651 | ||
|
|
e4d58a0a88 |
2
.github/workflows/test-build.yaml
vendored
2
.github/workflows/test-build.yaml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ '17' ]
|
||||
java: ['17', '21']
|
||||
name: Test with Java ${{ matrix.Java }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -66,6 +66,9 @@ forge-gui-mobile-dev/testAssets
|
||||
|
||||
forge-gui/res/cardsfolder/*.bat
|
||||
|
||||
# Generated changelog file
|
||||
forge-gui/release-files/CHANGES.txt
|
||||
|
||||
forge-gui/res/PerSetTrackingResults
|
||||
forge-gui/res/decks
|
||||
forge-gui/res/layouts
|
||||
@@ -87,3 +90,7 @@ forge-gui/tools/PerSetTrackingResults
|
||||
*.tiled-session
|
||||
/forge-gui/res/adventure/*.tiled-project
|
||||
/forge-gui/res/adventure/*.tiled-session
|
||||
|
||||
# Ignore python temporaries
|
||||
__pycache__
|
||||
*.pyc
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
Summary
|
||||
|
||||
(Summarize the bug encountered concisely)
|
||||
|
||||
|
||||
Steps to reproduce
|
||||
|
||||
(How one can reproduce the issue - this is very important. Specific cards and specific actions especially)
|
||||
|
||||
|
||||
Which version of Forge are you on (Release, Snapshot? Desktop, Android?)
|
||||
|
||||
|
||||
What is the current bug behavior?
|
||||
|
||||
(What actually happens)
|
||||
|
||||
|
||||
What is the expected correct behavior?
|
||||
|
||||
(What you should see instead)
|
||||
|
||||
|
||||
Relevant logs and/or screenshots
|
||||
|
||||
(Paste/Attach your game.log from the crash - please use code blocks (```)) Also, provide screenshots of the current state.
|
||||
|
||||
|
||||
Possible fixes
|
||||
|
||||
(If you can, link to the line of code that might be responsible for the problem)
|
||||
|
||||
/label ~needs-investigation
|
||||
@@ -1,15 +0,0 @@
|
||||
Summary
|
||||
|
||||
(Summarize the feature you wish concisely)
|
||||
|
||||
|
||||
Example screenshots
|
||||
|
||||
(If this is a UI change, please provide an example screenshot of how this feature might work)
|
||||
|
||||
|
||||
Feature type
|
||||
|
||||
(Where in Forge does this belong? e.g. Quest Mode, Deck Editor, Limited, Constructed, etc.)
|
||||
|
||||
/label ~feature request
|
||||
@@ -15,7 +15,7 @@ public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
GuiBase.setInterface(new GuiMobile(Files.exists(Paths.get("./res"))?"./":"../forge-gui/"));
|
||||
GuiBase.setDeviceInfo("", "", 0, 0);
|
||||
GuiBase.setDeviceInfo(null, 0, 0);
|
||||
new EditorMainWindow(Config.instance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +563,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||
if (thisRemove > 0) {
|
||||
removed += thisRemove;
|
||||
table.put(null, prefCard, CounterType.get(cType), thisRemove);
|
||||
table.put(null, prefCard, cType, thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,7 +573,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
@Override
|
||||
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||
final int c = cost.getAbilityAmount(ability);
|
||||
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
|
||||
final Card originalHost = ObjectUtils.getIfNull(ability.getOriginalHost(), source);
|
||||
|
||||
if (c <= 0) {
|
||||
return null;
|
||||
@@ -716,7 +716,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(null, crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||
table.put(null, crd, CounterEnumType.QUEST, over);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,7 +767,7 @@ public class ComputerUtil {
|
||||
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
|
||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
||||
|
||||
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
|
||||
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterEnumType.STUN));
|
||||
|
||||
if (untap) {
|
||||
typeList.remove(activate);
|
||||
@@ -2542,7 +2542,7 @@ public class ComputerUtil {
|
||||
|
||||
boolean opponent = controller.isOpponentOf(ai);
|
||||
|
||||
final CounterType p1p1Type = CounterType.get(CounterEnumType.P1P1);
|
||||
final CounterType p1p1Type = CounterEnumType.P1P1;
|
||||
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return Aggregates.random(options);
|
||||
|
||||
@@ -171,7 +171,7 @@ public class SpecialAiLogic {
|
||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterEnumType.POISON)) {
|
||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ public class SpecialAiLogic {
|
||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterEnumType.POISON)) {
|
||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||
}
|
||||
|
||||
|
||||
@@ -499,19 +499,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// Fetching should occur fairly often as it helps cast more spells, and
|
||||
// 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<>();
|
||||
if (sa.hasParam("Origin")) {
|
||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||
@@ -1607,7 +1594,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
} else if (logic.startsWith("ExilePreference")) {
|
||||
return doExilePreferenceLogic(decider, sa, fetchList);
|
||||
} else if (logic.equals("BounceOwnTrigger")) {
|
||||
return doBounceOwnTriggerLogic(decider, fetchList);
|
||||
return doBounceOwnTriggerLogic(decider, sa, fetchList);
|
||||
}
|
||||
}
|
||||
if (fetchList.isEmpty()) {
|
||||
@@ -2171,16 +2158,18 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.BOUNCED_THIS_TURN);
|
||||
}
|
||||
|
||||
private static Card doBounceOwnTriggerLogic(Player ai, CardCollection choices) {
|
||||
private static Card doBounceOwnTriggerLogic(Player ai, SpellAbility sa, CardCollection choices) {
|
||||
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));
|
||||
if (!prefChoices.isEmpty()) {
|
||||
return ComputerUtilCard.getBestAI(prefChoices);
|
||||
} else if (!unprefChoices.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(unprefChoices);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (!unprefChoices.isEmpty() && sa.getSubAbility() != null) {
|
||||
// some extra benefit like First Responder
|
||||
return ComputerUtilCard.getWorstAI(unprefChoices);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -102,7 +102,7 @@ public abstract class CountersAi extends SpellAbilityAi {
|
||||
} else if (type.equals("DIVINITY")) {
|
||||
final CardCollection boon = CardLists.filter(list, c -> c.getCounters(CounterEnumType.DIVINITY) == 0);
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon);
|
||||
} else if (CounterType.get(type).isKeywordCounter()) {
|
||||
} else if (CounterType.getType(type).isKeywordCounter()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(CardLists.getNotKeyword(list, type));
|
||||
} else {
|
||||
// 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)) {
|
||||
addTargetsByCounterType(ai, sa, aiList, CounterType.get(type));
|
||||
addTargetsByCounterType(ai, sa, aiList, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
if (!oppList.isEmpty()) {
|
||||
// not enough targets
|
||||
if (sa.canAddMoreTarget()) {
|
||||
final CounterType type = CounterType.get(CounterEnumType.M1M1);
|
||||
final CounterType type = CounterEnumType.M1M1;
|
||||
if (counterType == null || counterType == 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) {
|
||||
// Proliferate is always optional for all, no need to select best
|
||||
|
||||
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
||||
final CounterType poison = CounterEnumType.POISON;
|
||||
|
||||
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
||||
// because countertype can't be chosen anymore, only look for poison counters
|
||||
|
||||
@@ -170,7 +170,7 @@ public class CountersPutAi extends CountersAi {
|
||||
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
||||
|
||||
oppCreatM1 = CardLists.filter(oppCreatM1, input -> input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1)));
|
||||
oppCreatM1 = CardLists.filter(oppCreatM1, input -> input.getNetToughness() <= 1 && input.canReceiveCounters(CounterEnumType.M1M1));
|
||||
|
||||
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
||||
if (best != null) {
|
||||
@@ -336,7 +336,7 @@ public class CountersPutAi extends CountersAi {
|
||||
Game game = ai.getGame();
|
||||
Combat combat = game.getCombat();
|
||||
|
||||
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||
if (!source.canReceiveCounters(CounterEnumType.P1P1) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return doCombatAdaptLogic(source, amount, combat);
|
||||
@@ -608,7 +608,7 @@ public class CountersPutAi extends CountersAi {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||
}
|
||||
|
||||
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
||||
final int currCounters = cards.get(0).getCounters(CounterType.getType(type));
|
||||
// each non +1/+1 counter on the card is a 10% chance of not
|
||||
// activating this ability.
|
||||
|
||||
@@ -623,7 +623,7 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
|
||||
// Useless since the card already has the keyword (or for another reason)
|
||||
if (ComputerUtil.isUselessCounter(CounterType.get(type), cards.get(0))) {
|
||||
if (ComputerUtil.isUselessCounter(CounterType.getType(type), cards.get(0))) {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
}
|
||||
@@ -961,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) {
|
||||
// Bolster does use this
|
||||
// TODO need more or less logic there?
|
||||
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||
final CounterType m1m1 = CounterEnumType.M1M1;
|
||||
final CounterType p1p1 = CounterEnumType.P1P1;
|
||||
|
||||
// no logic if there is no options or no to choice
|
||||
if (!isOptional && Iterables.size(options) <= 1) {
|
||||
@@ -1083,8 +1083,8 @@ public class CountersPutAi extends CountersAi {
|
||||
if (e instanceof Card) {
|
||||
Card c = (Card) e;
|
||||
if (c.getController().isOpponentOf(ai)) {
|
||||
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
if (options.contains(CounterEnumType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterEnumType.M1M1;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, c)) {
|
||||
@@ -1101,12 +1101,12 @@ public class CountersPutAi extends CountersAi {
|
||||
} else if (e instanceof Player) {
|
||||
Player p = (Player) e;
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (options.contains(CounterType.get(CounterEnumType.POISON))) {
|
||||
return CounterType.get(CounterEnumType.POISON);
|
||||
if (options.contains(CounterEnumType.POISON)) {
|
||||
return CounterEnumType.POISON;
|
||||
}
|
||||
} else {
|
||||
if (options.contains(CounterType.get(CounterEnumType.EXPERIENCE))) {
|
||||
return CounterType.get(CounterEnumType.EXPERIENCE);
|
||||
if (options.contains(CounterEnumType.EXPERIENCE)) {
|
||||
return CounterEnumType.EXPERIENCE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -218,18 +218,18 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
Card tgt = (Card) params.get("Target");
|
||||
|
||||
// planeswalker has high priority for loyalty counters
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
|
||||
return CounterType.get(CounterEnumType.LOYALTY);
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterEnumType.LOYALTY)) {
|
||||
return CounterEnumType.LOYALTY;
|
||||
}
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
// creatures with BaseToughness below or equal zero might be
|
||||
// killed if their counters are removed
|
||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||
if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
if (options.contains(CounterEnumType.P1P1)) {
|
||||
return CounterEnumType.P1P1;
|
||||
} else if (options.contains(CounterEnumType.M1M1)) {
|
||||
return CounterEnumType.M1M1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,17 +241,17 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
// this counters are treat first to be removed
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterEnumType.ICE)) {
|
||||
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
||||
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
|
||||
|
||||
if (maritEmpty) {
|
||||
return CounterType.get(CounterEnumType.ICE);
|
||||
return CounterEnumType.ICE;
|
||||
}
|
||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterEnumType.P1P1)) {
|
||||
return CounterEnumType.P1P1;
|
||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterEnumType.M1M1)) {
|
||||
return CounterEnumType.M1M1;
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to add more
|
||||
|
||||
@@ -384,7 +384,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
if (targetCard.getController().isOpponentOf(ai)) {
|
||||
// if its a Planeswalker try to remove Loyality first
|
||||
if (targetCard.isPlaneswalker()) {
|
||||
return CounterType.get(CounterEnumType.LOYALTY);
|
||||
return CounterEnumType.LOYALTY;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||
@@ -392,10 +392,10 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
} else if (options.contains(CounterType.get(CounterEnumType.P1P1)) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
if (options.contains(CounterEnumType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||
return CounterEnumType.M1M1;
|
||||
} else if (options.contains(CounterEnumType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterEnumType.P1P1;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||
|
||||
@@ -133,9 +133,9 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
||||
// When using Pestilence to hurt players, do it at
|
||||
// the end of the opponent's turn only
|
||||
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|
||||
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
|
||||
if (!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic"))
|
||||
|| (ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||
&& !ai.getGame().getPhaseHandler().isPlayerTurn(ai)))
|
||||
// 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!
|
||||
// || (ai.sa.getPayCosts(). ??? )
|
||||
|
||||
@@ -26,7 +26,6 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -370,7 +369,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
|
||||
// try to make opponent lose to poison
|
||||
// currently only Caress of Phyrexia
|
||||
if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (getPoison != null && oppA.canReceiveCounters(CounterEnumType.POISON)) {
|
||||
if (oppA.getPoisonCounters() + numCards > 9) {
|
||||
sa.getTargets().add(oppA);
|
||||
return true;
|
||||
@@ -414,7 +413,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (getPoison != null && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (getPoison != null && ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||
if (numCards + ai.getPoisonCounters() >= 8) {
|
||||
aiTarget = false;
|
||||
}
|
||||
@@ -472,7 +471,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// ally would lose because of poison
|
||||
if (getPoison != null && ally.canReceiveCounters(CounterType.get(CounterEnumType.POISON)) && ally.getPoisonCounters() + numCards > 9) {
|
||||
if (getPoison != null && ally.canReceiveCounters(CounterEnumType.POISON) && ally.getPoisonCounters() + numCards > 9) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.ai.AiAbilityDecision;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.CostPayLife;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -36,6 +34,26 @@ public class EndureAi extends SpellAbilityAi {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ public class ManaAi extends SpellAbilityAi {
|
||||
int numCounters = 0;
|
||||
int manaSurplus = 0;
|
||||
if ("Count$xPaid".equals(host.getSVar("X")) && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||
CounterType ctrType = CounterType.get(CounterEnumType.KI); // Petalmane Baku
|
||||
CounterType ctrType = CounterEnumType.KI; // Petalmane Baku
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
ctrType = ((CostRemoveCounter)part).counter;
|
||||
|
||||
@@ -6,7 +6,6 @@ import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.GameLossReason;
|
||||
@@ -65,7 +64,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
boolean result;
|
||||
if (sa.usesTargeting()) {
|
||||
result = tgtPlayer(ai, sa, mandatory);
|
||||
} else if (mandatory || !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
} else if (mandatory || !ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||
// mandatory or ai is uneffected
|
||||
result = true;
|
||||
} else {
|
||||
@@ -90,7 +89,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
PlayerCollection betterTgts = tgts.filter(input -> {
|
||||
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
||||
return false;
|
||||
} else if (!input.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
} else if (!input.canReceiveCounters(CounterEnumType.POISON)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -109,7 +108,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
if (tgts.isEmpty()) {
|
||||
if (mandatory) {
|
||||
// AI is uneffected
|
||||
if (ai.canBeTargetedBy(sa) && !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (ai.canBeTargetedBy(sa) && !ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
@@ -121,7 +120,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
if (input.cantLoseCheck(GameLossReason.Poisoned)) {
|
||||
return true;
|
||||
}
|
||||
return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
|
||||
return !input.canReceiveCounters(CounterEnumType.POISON);
|
||||
});
|
||||
if (!betterAllies.isEmpty()) {
|
||||
allies = betterAllies;
|
||||
|
||||
@@ -8,7 +8,6 @@ import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerController;
|
||||
@@ -40,7 +39,7 @@ public class TimeTravelAi extends SpellAbilityAi {
|
||||
// so removing them is good; stuff on the battlefield is usually stuff like Vanishing or As Foretold, which favors adding Time
|
||||
// counters for better effect, but exceptions should be added here).
|
||||
Card target = (Card)params.get("Target");
|
||||
return !ComputerUtil.isNegativeCounter(CounterType.get(CounterEnumType.TIME), target);
|
||||
return !ComputerUtil.isNegativeCounter(CounterEnumType.TIME, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,9 +18,9 @@ import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.regex.Pattern;
|
||||
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
|
||||
*
|
||||
@@ -29,8 +29,6 @@ import java.util.stream.Collectors;
|
||||
public class StaticData {
|
||||
private final CardStorageReader cardReader;
|
||||
private final CardStorageReader tokenReader;
|
||||
private final CardStorageReader customCardReader;
|
||||
|
||||
private final String blockDataFolder;
|
||||
private final CardDb commonCards;
|
||||
private final CardDb variantCards;
|
||||
@@ -79,7 +77,6 @@ public class StaticData {
|
||||
this.tokenReader = tokenReader;
|
||||
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
||||
this.blockDataFolder = blockDataFolder;
|
||||
this.customCardReader = customCardReader;
|
||||
this.allowCustomCardsInDecksConformance = allowCustomCardsInDecksConformance;
|
||||
this.enableSmartCardArtSelection = enableSmartCardArtSelection;
|
||||
this.loadNonLegalCards = loadNonLegalCards;
|
||||
@@ -784,6 +781,7 @@ public class StaticData {
|
||||
Queue<String> TOKEN_Q = new ConcurrentLinkedQueue<>();
|
||||
boolean nifHeader = false;
|
||||
boolean cniHeader = false;
|
||||
final Pattern funnyCardCollectorNumberPattern = Pattern.compile("^F\\d+");
|
||||
for (CardEdition e : editions) {
|
||||
if (CardEdition.Type.FUNNY.equals(e.getType()))
|
||||
continue;
|
||||
@@ -791,11 +789,13 @@ public class StaticData {
|
||||
Map<String, Pair<Boolean, Integer>> cardCount = new HashMap<>();
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
for (CardEdition.EditionEntry c : e.getObtainableCards()) {
|
||||
int amount = 1;
|
||||
|
||||
if (cardCount.containsKey(c.name())) {
|
||||
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));
|
||||
amount = cardCount.get(c.name()).getRight() + 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...
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardEdition.EditionEntry;
|
||||
import forge.card.CardEdition.Type;
|
||||
@@ -200,7 +201,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -241,8 +242,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
setCode = info[index];
|
||||
index++;
|
||||
}
|
||||
if(info.length > index && isArtIndex(info[index])) {
|
||||
artIndex = Integer.parseInt(info[index]);
|
||||
if(info.length > index && isArtIndex(info[index].replace(ImageKeys.BACKFACE_POSTFIX, ""))) {
|
||||
artIndex = Integer.parseInt(info[index].replace(ImageKeys.BACKFACE_POSTFIX, ""));
|
||||
index++;
|
||||
}
|
||||
if(info.length > index && isCollectorNumber(info[index])) {
|
||||
|
||||
@@ -552,26 +552,16 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
public List<PrintSheet> getPrintSheetsBySection() {
|
||||
final CardDb cardDb = StaticData.instance().getCommonCards();
|
||||
Map<String, Integer> cardToIndex = new HashMap<>();
|
||||
|
||||
List<PrintSheet> sheets = Lists.newArrayList();
|
||||
for (String sectionName : cardMap.keySet()) {
|
||||
if (sectionName.equals(EditionSectionWithCollectorNumbers.CONJURED.getName())) {
|
||||
for (Map.Entry<String, java.util.Collection<EditionEntry>> section : cardMap.asMap().entrySet()) {
|
||||
if (section.getKey().equals(EditionSectionWithCollectorNumbers.CONJURED.getName())) {
|
||||
continue;
|
||||
}
|
||||
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sectionName));
|
||||
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), section.getKey()));
|
||||
|
||||
List<EditionEntry> cards = cardMap.get(sectionName);
|
||||
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);
|
||||
for (EditionEntry card : section.getValue()) {
|
||||
sheet.add(cardDb.getCard(card.name, this.getCode(), card.collectorNumber));
|
||||
}
|
||||
|
||||
sheets.add(sheet);
|
||||
@@ -659,31 +649,37 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// parse sections of the format "<collector number> <rarity> <name>"
|
||||
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
||||
for(String line : contents.get(sectionName)) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String collectorNumber = matcher.group(2);
|
||||
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
||||
String cardName = matcher.group(5);
|
||||
String artistName = matcher.group(7);
|
||||
String functionalVariantName = matcher.group(9);
|
||||
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
|
||||
|
||||
cardMap.put(sectionName, cis);
|
||||
}
|
||||
} else if (boosterSlotsToParse.contains(sectionName)) {
|
||||
// parse booster slots of the format "Base=N\n|Replace=<amount> <sheet>"
|
||||
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
|
||||
if (sectionName.endsWith("Types")) {
|
||||
CardType.Helper.parseTypes(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));
|
||||
// Parse cards
|
||||
|
||||
// parse sections of the format "<collector number> <rarity> <name>"
|
||||
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
|
||||
for(String line : contents.get(sectionName)) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String collectorNumber = matcher.group(2);
|
||||
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
||||
String cardName = matcher.group(5);
|
||||
String artistName = matcher.group(7);
|
||||
String functionalVariantName = matcher.group(9);
|
||||
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
|
||||
|
||||
cardMap.put(sectionName, cis);
|
||||
}
|
||||
} else if (boosterSlotsToParse.contains(sectionName)) {
|
||||
// parse booster slots of the format "Base=N\n|Replace=<amount> <sheet>"
|
||||
boosterSlots.add(BoosterSlot.parseSlot(sectionName, contents.get(sectionName)));
|
||||
} else {
|
||||
// save custom print sheets of the format "<amount> <name>|<setcode>|<art index>"
|
||||
// to parse later when printsheets are loaded lazily (and the cardpool is already initialized)
|
||||
customPrintSheetsToParse.put(sectionName, contents.get(sectionName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,7 +846,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
@Override
|
||||
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");
|
||||
else map.put(item.getName(), item);
|
||||
else map.put(item.getCode(), item);
|
||||
}
|
||||
public void append(CardEdition.Collection C){ //Append custom editions
|
||||
if (lock) throw new UnsupportedOperationException("This is a read-only storage");
|
||||
|
||||
@@ -53,6 +53,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
private boolean addsWildCardColor;
|
||||
private int setColorID;
|
||||
private boolean custom;
|
||||
private boolean unsupported;
|
||||
private String path;
|
||||
|
||||
public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
|
||||
@@ -220,7 +221,9 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public boolean isCustom() { return custom; }
|
||||
public void setCustom() { custom = true; }
|
||||
public void setCustom() { custom = true; }
|
||||
|
||||
public boolean isUnsupported() { return unsupported; }
|
||||
|
||||
@Override
|
||||
public CardType getType() {
|
||||
@@ -361,16 +364,21 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public boolean isDoctor() {
|
||||
Set<String> subtypes = new HashSet<>();
|
||||
for (String type : mainPart.getType().getSubtypes()) {
|
||||
if (!type.equals("Time Lord") && !type.equals("Doctor")) {
|
||||
return false;
|
||||
}
|
||||
subtypes.add(type);
|
||||
}
|
||||
return true;
|
||||
|
||||
return subtypes.size() == 2 &&
|
||||
subtypes.contains("Time Lord") &&
|
||||
subtypes.contains("Doctor");
|
||||
}
|
||||
|
||||
public boolean canBeOathbreaker() {
|
||||
CardType type = mainPart.getType();
|
||||
if (mainPart.getOracleText().contains("can be your commander")) {
|
||||
return true;
|
||||
}
|
||||
return type.isPlaneswalker();
|
||||
}
|
||||
|
||||
@@ -823,6 +831,8 @@ public final class CardRules implements ICardCharacteristics {
|
||||
faces[0].assignMissingFields();
|
||||
final CardRules result = new CardRules(faces, CardSplitType.None, cah);
|
||||
|
||||
result.unsupported = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -189,6 +189,38 @@ public final class CardRulesPredicates {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -1066,4 +1066,74 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
@@ -68,6 +69,13 @@ public class PrintSheet {
|
||||
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) {
|
||||
int sum = start;
|
||||
boolean isSecondRun = start > 0;
|
||||
@@ -85,15 +93,6 @@ public class PrintSheet {
|
||||
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) {
|
||||
int count=0;
|
||||
for (Entry<PaperCard, Integer> kv : cardsWithWeights) {
|
||||
@@ -144,7 +143,7 @@ public class PrintSheet {
|
||||
return cardsWithWeights.isEmpty();
|
||||
}
|
||||
|
||||
public Iterable<PaperCard> toFlatList() {
|
||||
public List<PaperCard> toFlatList() {
|
||||
return cardsWithWeights.toFlatList();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public class CardPool extends ItemPool<PaperCard> {
|
||||
private static final long serialVersionUID = -5379091255613968393L;
|
||||
|
||||
@@ -78,12 +77,20 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
|
||||
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
|
||||
CardDb db = entry.getValue();
|
||||
|
||||
PaperCard paperCard = db.getCard(cardName, setCode, collectorNumber, flags);
|
||||
if (paperCard != null) {
|
||||
this.add(paperCard, amount);
|
||||
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?
|
||||
this.add(cardName, setCode, IPaperCard.NO_ART_INDEX, amount, addAny, flags);
|
||||
}
|
||||
@@ -419,6 +426,12 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
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) {
|
||||
List<Pair<String, Integer>> cardRequests = new ArrayList<>();
|
||||
if (lines == null)
|
||||
@@ -468,6 +481,7 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
* @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
|
||||
*/
|
||||
@Override
|
||||
public CardPool getFilteredPool(Predicate<PaperCard> predicate) {
|
||||
CardPool filteredPool = new CardPool();
|
||||
for (PaperCard c : this.items.keySet()) {
|
||||
|
||||
@@ -28,6 +28,8 @@ import forge.item.PaperCard;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serial;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@@ -208,14 +210,19 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
super.cloneFieldsTo(clone);
|
||||
final Deck result = (Deck) clone;
|
||||
loadDeferredSections();
|
||||
for (Entry<DeckSection, CardPool> kv : parts.entrySet()) {
|
||||
CardPool cp = new CardPool();
|
||||
result.parts.put(kv.getKey(), cp);
|
||||
cp.addAll(kv.getValue());
|
||||
// parts shouldn't be null
|
||||
if (parts != null) {
|
||||
for (Entry<DeckSection, CardPool> kv : parts.entrySet()) {
|
||||
CardPool cp = new CardPool();
|
||||
result.parts.put(kv.getKey(), cp);
|
||||
cp.addAll(kv.getValue());
|
||||
}
|
||||
}
|
||||
result.setAiHints(StringUtils.join(aiHints, " | "));
|
||||
result.setDraftNotes(draftNotes);
|
||||
tags.addAll(result.getTags());
|
||||
//noinspection ConstantValue
|
||||
if(tags != null) //Can happen deserializing old Decks.
|
||||
result.tags.addAll(this.tags);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -521,6 +528,17 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
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) {
|
||||
if (aiHintsInfo == null || aiHintsInfo.trim().isEmpty()) {
|
||||
return;
|
||||
@@ -614,6 +632,14 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
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} */
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
@@ -663,4 +689,4 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
}
|
||||
return totalCount == 0 ? 0 : Math.round(totalCMC / totalCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,8 @@ import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.Range;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
@@ -60,6 +57,13 @@ public enum DeckFormat {
|
||||
//Limited contraption decks have no restrictions.
|
||||
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,
|
||||
card -> StaticData.instance().getCommanderPredicate().test(card)
|
||||
@@ -108,7 +112,13 @@ public enum DeckFormat {
|
||||
}
|
||||
},
|
||||
PlanarConquest ( Range.of(40, Integer.MAX_VALUE), Range.is(0), 1),
|
||||
Adventure ( Range.of(40, Integer.MAX_VALUE), Range.of(0, 15), 4),
|
||||
Adventure ( Range.of(40, Integer.MAX_VALUE), Range.of(0, Integer.MAX_VALUE), 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),
|
||||
Planechase ( Range.of(60, Integer.MAX_VALUE), Range.is(0), 4),
|
||||
Archenemy ( Range.of(60, Integer.MAX_VALUE), Range.is(0), 4),
|
||||
@@ -191,12 +201,57 @@ public enum DeckFormat {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maxCardCopies
|
||||
* @return the default maximum copies of a card in this format.
|
||||
*/
|
||||
public int getMaxCardCopies() {
|
||||
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) {
|
||||
if (deck == null) {
|
||||
return "is not selected";
|
||||
@@ -353,7 +408,7 @@ public enum DeckFormat {
|
||||
// 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))) {
|
||||
IPaperCard simpleCard = StaticData.instance().getCommonCards().getCard(cp.getKey());
|
||||
if (simpleCard != null && simpleCard.getRules().isCustom() && !StaticData.instance().allowCustomCardsInDecksConformance())
|
||||
if (simpleCard != null && simpleCard.getRules().isCustom() && !allowCustomCards())
|
||||
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
|
||||
if (simpleCard == null) {
|
||||
@@ -484,6 +539,10 @@ public enum DeckFormat {
|
||||
// Not needed by default
|
||||
}
|
||||
|
||||
public boolean allowCustomCards() {
|
||||
return StaticData.instance().allowCustomCardsInDecksConformance();
|
||||
}
|
||||
|
||||
public boolean isLegalCard(PaperCard pc) {
|
||||
if (cardPoolFilter == null) {
|
||||
if (paperCardPoolFilter == null) {
|
||||
@@ -498,13 +557,13 @@ public enum DeckFormat {
|
||||
if (cardPoolFilter != null && !cardPoolFilter.test(rules)) {
|
||||
return false;
|
||||
}
|
||||
if (this.equals(DeckFormat.Oathbreaker)) {
|
||||
if (this == DeckFormat.Oathbreaker) {
|
||||
return rules.canBeOathbreaker();
|
||||
}
|
||||
if (this.equals(DeckFormat.Brawl)) {
|
||||
if (this == DeckFormat.Brawl) {
|
||||
return rules.canBeBrawlCommander();
|
||||
}
|
||||
if (this.equals(DeckFormat.TinyLeaders)) {
|
||||
if (this == DeckFormat.TinyLeaders) {
|
||||
return rules.canBeTinyLeadersCommander();
|
||||
}
|
||||
return rules.canBeCommander();
|
||||
@@ -553,6 +612,8 @@ public enum DeckFormat {
|
||||
for (final PaperCard p : commanders) {
|
||||
cmdCI |= p.getRules().getColorIdentity().getColor();
|
||||
}
|
||||
if(cmdCI == MagicColor.ALL_COLORS)
|
||||
return x -> true;
|
||||
Predicate<CardRules> predicate = CardRulesPredicates.hasColorIdentity(cmdCI);
|
||||
if (commanders.size() == 1 && commanders.get(0).getRules().canBePartnerCommander()) {
|
||||
// Also show available partners a commander can have a partner.
|
||||
|
||||
@@ -46,7 +46,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
|
||||
// These fields are kinda PK for PrintedCard
|
||||
private final String name;
|
||||
private final String edition;
|
||||
private String edition;
|
||||
/* [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.
|
||||
(see getCollectorNumber())
|
||||
@@ -154,6 +154,31 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
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() {
|
||||
if(this.flaglessVersion == null) {
|
||||
if(this.flags == PaperCardFlags.IDENTITY_FLAGS)
|
||||
@@ -225,7 +250,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
||||
this.foil = foil;
|
||||
this.rarity = rarity;
|
||||
this.artist = TextUtil.normalizeText(artist);
|
||||
this.artist = artist;
|
||||
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.
|
||||
// This is a good tradeoff
|
||||
@@ -350,7 +375,8 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
|
||||
pc = readObjectAlternate(name, edition);
|
||||
if (pc == null) {
|
||||
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
||||
pc = StaticData.instance().getCommonCards().createUnsupportedCard(name);
|
||||
//throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
||||
}
|
||||
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
|
||||
}
|
||||
|
||||
@@ -50,6 +50,13 @@ public abstract class PaperCardPredicates {
|
||||
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 final CardRarity matchingRarity;
|
||||
|
||||
@@ -93,6 +100,17 @@ 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 final CardRarity operand;
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ public class SealedTemplate {
|
||||
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 String name;
|
||||
|
||||
@@ -254,7 +254,7 @@ public class BoosterGenerator {
|
||||
|
||||
if (sheetKey.startsWith("wholeSheet")) {
|
||||
PrintSheet ps = getPrintSheet(sheetKey);
|
||||
result.addAll(ps.all());
|
||||
result.addAll(ps.toFlatList());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ public class BoosterGenerator {
|
||||
PrintSheet replaceThis = tryGetStaticSheet(split[0]);
|
||||
List<PaperCard> candidates = Lists.newArrayList();
|
||||
for (PaperCard p : result) {
|
||||
if (replaceThis.all().contains(p)) {
|
||||
if (replaceThis.contains(p)) {
|
||||
candidates.add(candidates.size(), p);
|
||||
}
|
||||
}
|
||||
@@ -398,7 +398,7 @@ public class BoosterGenerator {
|
||||
PrintSheet replaceThis = tryGetStaticSheet(split[0]);
|
||||
List<PaperCard> candidates = Lists.newArrayList();
|
||||
for (PaperCard p : result) {
|
||||
if (replaceThis.all().contains(p)) {
|
||||
if (replaceThis.contains(p)) {
|
||||
candidates.add(candidates.size(), p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,11 +199,8 @@ public class ImageUtil {
|
||||
return getImageRelativePath(cp, face, true, true);
|
||||
}
|
||||
|
||||
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){
|
||||
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop){
|
||||
String editionCode;
|
||||
if (setCode != null && !setCode.isEmpty())
|
||||
editionCode = setCode;
|
||||
@@ -222,29 +219,37 @@ public class ImageUtil {
|
||||
} else if (cardCollectorNumber.startsWith("OPC2")) {
|
||||
editionCode = "opc2";
|
||||
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 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")) {
|
||||
|
||||
// Only the bottom half of a meld card shares a collector number.
|
||||
// Hanweir Garrison EMN already has a appended.
|
||||
// Exception: The front facing card doesn't use a in FIN
|
||||
if (face.equals("back")) {
|
||||
cardCollectorNumber += "b";
|
||||
} else if (!editionCode.equals("fin")) {
|
||||
cardCollectorNumber += "a";
|
||||
if (cp.getRules().getSplitType() == CardSplitType.Meld) {
|
||||
if (face.equals("back")) {
|
||||
PaperCard meldBasePc = cp.getMeldBaseCard();
|
||||
cardCollectorNumber = meldBasePc.getCollectorNumber();
|
||||
String collectorNumberSuffix = "";
|
||||
|
||||
if (cardCollectorNumber.endsWith("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");
|
||||
}
|
||||
|
||||
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, encodeUtf8(cardCollectorNumber),
|
||||
|
||||
@@ -269,11 +269,16 @@ 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
|
||||
}
|
||||
|
||||
public void removeIf(Predicate<T> test) {
|
||||
for (final T item : items.keySet()) {
|
||||
if (test.test(item))
|
||||
remove(item);
|
||||
}
|
||||
public void removeIf(Predicate<T> filter) {
|
||||
items.keySet().removeIf(filter);
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -285,4 +290,19 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
|
||||
return (obj instanceof ItemPool ip) &&
|
||||
(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>
|
||||
<groupId>io.sentry</groupId>
|
||||
<artifactId>sentry-logback</artifactId>
|
||||
<version>8.18.0</version>
|
||||
<version>8.19.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jgrapht</groupId>
|
||||
|
||||
@@ -414,19 +414,6 @@ public class Game {
|
||||
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).
|
||||
* <i>Use this in UI and after match calculations</i>
|
||||
|
||||
@@ -1822,8 +1822,8 @@ public class GameAction {
|
||||
|
||||
private boolean stateBasedAction704_5q(Card c) {
|
||||
boolean checkAgain = false;
|
||||
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||
final CounterType p1p1 = CounterEnumType.P1P1;
|
||||
final CounterType m1m1 = CounterEnumType.M1M1;
|
||||
int plusOneCounters = c.getCounters(p1p1);
|
||||
int minusOneCounters = c.getCounters(m1m1);
|
||||
if (plusOneCounters > 0 && minusOneCounters > 0) {
|
||||
@@ -1843,7 +1843,7 @@ public class GameAction {
|
||||
return checkAgain;
|
||||
}
|
||||
private boolean stateBasedAction704_5r(Card c) {
|
||||
final CounterType dreamType = CounterType.get(CounterEnumType.DREAM);
|
||||
final CounterType dreamType = CounterEnumType.DREAM;
|
||||
|
||||
int old = c.getCounters(dreamType);
|
||||
if (old <= 0) {
|
||||
|
||||
@@ -33,7 +33,6 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.event.GameEventCardAttachment;
|
||||
import forge.game.keyword.Keyword;
|
||||
@@ -305,9 +304,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
Integer value = counters.get(counterName);
|
||||
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) {
|
||||
if (num <= 0) {
|
||||
@@ -316,9 +312,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
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);
|
||||
|
||||
@@ -328,10 +321,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
abstract public int subtractCounter(final CounterType counterName, final int n, final Player remover);
|
||||
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) {
|
||||
if (n <= 0 || !canReceiveCounters(counterType)) {
|
||||
// As per rule 107.1b
|
||||
@@ -351,18 +340,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
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);
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ public enum GameLogEntryType {
|
||||
TURN("Turn"),
|
||||
MULLIGAN("Mulligan"),
|
||||
ANTE("Ante"),
|
||||
DRAFT("Draft"),
|
||||
ZONE_CHANGE("Zone Change"),
|
||||
PLAYER_CONTROL("Player control"),
|
||||
COMBAT("Combat"),
|
||||
|
||||
@@ -29,25 +29,25 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventGameOutcome ev) {
|
||||
// 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);
|
||||
|
||||
for (String outcome : ev.result.getOutcomeStrings()) {
|
||||
for (String outcome : ev.result().getOutcomeStrings()) {
|
||||
log.add(GameLogEntryType.GAME_OUTCOME, outcome);
|
||||
}
|
||||
return generateSummary(ev.history);
|
||||
return generateSummary(ev.history());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventScry ev) {
|
||||
String scryOutcome = "";
|
||||
|
||||
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));
|
||||
} else if (ev.toBottom == 0) {
|
||||
scryOutcome = localizer.getMessage("lblLogScryTopLibrary").replace("%s", ev.player.toString()).replace("%top", String.valueOf(ev.toTop));
|
||||
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()));
|
||||
} else if (ev.toBottom() == 0) {
|
||||
scryOutcome = localizer.getMessage("lblLogScryTopLibrary").replace("%s", ev.player().toString()).replace("%top", String.valueOf(ev.toTop()));
|
||||
} 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);
|
||||
@@ -57,12 +57,12 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
public GameLogEntry visit(GameEventSurveil ev) {
|
||||
String surveilOutcome = "";
|
||||
|
||||
if (ev.toLibrary > 0 && ev.toGraveyard > 0) {
|
||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibraryGraveyard", ev.player.toString(), String.valueOf(ev.toLibrary), String.valueOf(ev.toGraveyard));
|
||||
} else if (ev.toGraveyard == 0) {
|
||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibrary", ev.player.toString(), String.valueOf(ev.toLibrary));
|
||||
if (ev.toLibrary() > 0 && ev.toGraveyard() > 0) {
|
||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibraryGraveyard", ev.player().toString(), String.valueOf(ev.toLibrary()), String.valueOf(ev.toGraveyard()));
|
||||
} else if (ev.toGraveyard() == 0) {
|
||||
surveilOutcome = localizer.getMessage("lblLogSurveiledToLibrary", ev.player().toString(), String.valueOf(ev.toLibrary()));
|
||||
} 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);
|
||||
@@ -70,26 +70,26 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventSpellAbilityCast event) {
|
||||
String player = event.sa.getActivatingPlayer().getName();
|
||||
String action = event.sa.isSpell() ? localizer.getMessage("lblCast")
|
||||
: event.sa.isTrigger() ? localizer.getMessage("lblTriggered")
|
||||
String player = event.sa().getActivatingPlayer().getName();
|
||||
String action = event.sa().isSpell() ? localizer.getMessage("lblCast")
|
||||
: event.sa().isTrigger() ? localizer.getMessage("lblTriggered")
|
||||
: localizer.getMessage("lblActivated");
|
||||
String object = event.si.getStackDescription().startsWith("Morph ")
|
||||
String object = event.si().getStackDescription().startsWith("Morph ")
|
||||
? localizer.getMessage("lblMorph")
|
||||
: event.sa.getHostCard().toString();
|
||||
: event.sa().getHostCard().toString();
|
||||
|
||||
String messageForLog = "";
|
||||
|
||||
if (event.sa.getTargetRestrictions() != null) {
|
||||
if (event.sa().getTargetRestrictions() != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (TargetChoices ch : event.sa.getAllTargetChoices()) {
|
||||
for (TargetChoices ch : event.sa().getAllTargetChoices()) {
|
||||
if (null != ch) {
|
||||
sb.append(ch);
|
||||
}
|
||||
@@ -104,18 +104,18 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventCardModeChosen ev) {
|
||||
if (!ev.log) {
|
||||
if (!ev.log()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String modeChoiceOutcome;
|
||||
if (ev.random) {
|
||||
modeChoiceOutcome = localizer.getMessage("lblLogRandomMode", ev.cardName, ev.mode);
|
||||
if (ev.random()) {
|
||||
modeChoiceOutcome = localizer.getMessage("lblLogRandomMode", ev.cardName(), ev.mode());
|
||||
} else {
|
||||
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, "NICKNAME",
|
||||
Lang.getInstance().getNickName(name));
|
||||
@@ -124,7 +124,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
@@ -152,8 +152,8 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(final GameEventPlayerControl event) {
|
||||
final LobbyPlayer newLobbyPlayer = event.newLobbyPlayer;
|
||||
final Player p = event.player;
|
||||
final LobbyPlayer newLobbyPlayer = event.newLobbyPlayer();
|
||||
final Player p = event.player();
|
||||
|
||||
final String message;
|
||||
if (newLobbyPlayer == null) {
|
||||
@@ -166,23 +166,23 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventTurnPhase ev) {
|
||||
Player p = ev.playerTurn;
|
||||
return new GameLogEntry(GameLogEntryType.PHASE, ev.phaseDesc + Lang.getInstance().getPossessedObject(p.getName(), ev.phase.nameForUi));
|
||||
Player p = ev.playerTurn();
|
||||
return new GameLogEntry(GameLogEntryType.PHASE, ev.phaseDesc() + Lang.getInstance().getPossessedObject(p.getName(), ev.phase().nameForUi));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventCardDamaged event) {
|
||||
String additionalLog = "";
|
||||
if (event.type == DamageType.Deathtouch) {
|
||||
if (event.type() == DamageType.Deathtouch) {
|
||||
additionalLog = localizer.getMessage("lblDeathtouch");
|
||||
}
|
||||
if (event.type == DamageType.M1M1Counters) {
|
||||
if (event.type() == DamageType.M1M1Counters) {
|
||||
additionalLog = localizer.getMessage("lblAsM1M1Counters");
|
||||
}
|
||||
if (event.type == DamageType.LoyaltyLoss) {
|
||||
additionalLog = localizer.getMessage("lblRemovingNLoyaltyCounter", String.valueOf(event.amount));
|
||||
if (event.type() == DamageType.LoyaltyLoss) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -191,43 +191,43 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
*/
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventPlayerDamaged ev) {
|
||||
String extra = ev.infect ? localizer.getMessage("lblLogAsPoisonCounters") : "";
|
||||
String damageType = ev.combat ? localizer.getMessage("lblCombat") : localizer.getMessage("lblNonCombat");
|
||||
String message = localizer.getMessage("lblLogSourceDealsNDamageOfTypeToDest", ev.source.toString(),
|
||||
String.valueOf(ev.amount), damageType, ev.target.toString(), extra);
|
||||
String extra = ev.infect() ? localizer.getMessage("lblLogAsPoisonCounters") : "";
|
||||
String damageType = ev.combat() ? localizer.getMessage("lblCombat") : localizer.getMessage("lblNonCombat");
|
||||
String message = localizer.getMessage("lblLogSourceDealsNDamageOfTypeToDest", ev.source().toString(),
|
||||
String.valueOf(ev.amount()), damageType, ev.target().toString(), extra);
|
||||
return new GameLogEntry(GameLogEntryType.DAMAGE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventPlayerPoisoned ev) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventPlayerRadiation ev) {
|
||||
String message;
|
||||
final int change = ev.change;
|
||||
final int change = ev.change();
|
||||
String radCtr = CounterEnumType.RAD.getName().toLowerCase() + " " +
|
||||
Localizer.getInstance().getMessage("lblCounter").toLowerCase();
|
||||
if (change >= 0) message = localizer.getMessage("lblLogPlayerRadiation",
|
||||
ev.receiver.toString(), Lang.nounWithNumeralExceptOne(String.valueOf(change), radCtr),
|
||||
ev.source.toString());
|
||||
ev.receiver().toString(), Lang.nounWithNumeralExceptOne(String.valueOf(change), radCtr),
|
||||
ev.source().toString());
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -239,16 +239,16 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
// Append Defending Player/Planeswalker
|
||||
|
||||
// Not a big fan of the triple nested loop here
|
||||
for (GameEntity k : ev.attackersMap.keySet()) {
|
||||
Collection<Card> attackers = ev.attackersMap.get(k);
|
||||
for (GameEntity k : ev.attackersMap().keySet()) {
|
||||
Collection<Card> attackers = ev.attackersMap().get(k);
|
||||
if (attackers == null || attackers.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
@@ -262,7 +262,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
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();
|
||||
MapOfLists<Card, Card> attackers = kv.getValue();
|
||||
if (attackers == null || attackers.isEmpty()) {
|
||||
@@ -298,7 +298,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ public enum GameType {
|
||||
Tournament (DeckFormat.Constructed, false, true, true, "lblTournament", ""),
|
||||
CommanderGauntlet (DeckFormat.Commander, false, false, false, "lblCommanderGauntlet", "lblCommanderDesc"),
|
||||
Quest (DeckFormat.QuestDeck, true, true, false, "lblQuest", ""),
|
||||
QuestCommander (DeckFormat.Commander, true, true, false, "lblQuestCommander", ""),
|
||||
QuestDraft (DeckFormat.Limited, true, true, true, "lblQuestDraft", ""),
|
||||
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "lblPlanarConquest", ""),
|
||||
Adventure (DeckFormat.Adventure, true, false, false, "lblAdventure", ""),
|
||||
@@ -71,6 +72,8 @@ public enum GameType {
|
||||
return deck;
|
||||
});
|
||||
|
||||
private static final EnumSet<GameType> DRAFT_FORMATS = EnumSet.of(Draft, QuestDraft, AdventureEvent);
|
||||
|
||||
private final DeckFormat deckFormat;
|
||||
private final boolean isCardPoolLimited, canSideboard, addWonCardsMidGame;
|
||||
private final String name, englishName, description;
|
||||
@@ -87,7 +90,7 @@ public enum GameType {
|
||||
addWonCardsMidGame = addWonCardsMidgame0;
|
||||
name = localizer.getMessage(name0);
|
||||
englishName = localizer.getEnglishMessage(name0);
|
||||
if (description0.length()>0) {
|
||||
if (!description0.isEmpty()) {
|
||||
description0 = localizer.getMessage(description0);
|
||||
}
|
||||
description = description0;
|
||||
@@ -127,19 +130,8 @@ public enum GameType {
|
||||
return addWonCardsMidGame;
|
||||
}
|
||||
|
||||
public boolean isCommandZoneNeeded() {
|
||||
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 boolean isDraft() {
|
||||
return DRAFT_FORMATS.contains(this);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -153,6 +145,27 @@ public enum GameType {
|
||||
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) {
|
||||
return Enums.getIfPresent(GameType.class, name).orNull();
|
||||
}
|
||||
|
||||
@@ -215,6 +215,7 @@ public class GameView extends TrackableObject {
|
||||
}
|
||||
public void setDependencies(Table<StaticAbility, StaticAbility, Set<StaticAbilityLayer>> dependencies) {
|
||||
if (dependencies.isEmpty()) {
|
||||
set(TrackableProperty.Dependencies, "");
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -3,7 +3,6 @@ package forge.game.ability;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.spellability.AbilityActivated;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -18,14 +17,7 @@ public class AbilityApiBased extends AbilityActivated {
|
||||
api = api0;
|
||||
effect = api.getSpellEffect();
|
||||
|
||||
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);
|
||||
}
|
||||
effect.buildSpellAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -202,15 +202,6 @@ public final class AbilityFactory {
|
||||
final Card hostCard = state.getCard();
|
||||
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) {
|
||||
abCost = parseAbilityCost(state, mapParams, type);
|
||||
}
|
||||
|
||||
@@ -113,13 +113,6 @@ public class AbilityUtils {
|
||||
}
|
||||
} else if (defined.equals("Enchanted")) {
|
||||
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")) {
|
||||
final CardCollectionView grave = player.getCardsIn(ZoneType.Graveyard);
|
||||
|
||||
@@ -2345,6 +2338,9 @@ public class AbilityUtils {
|
||||
if (sq[0].equals("YourSpeed")) {
|
||||
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")) {
|
||||
return doXMath(calculateAmount(c, sq[game.isNight() ? 1 : 2], ctb), expr, c, ctb);
|
||||
|
||||
@@ -19,6 +19,7 @@ public enum ApiType {
|
||||
AddPhase (AddPhaseEffect.class),
|
||||
AddTurn (AddTurnEffect.class),
|
||||
AdvanceCrank (AdvanceCrankEffect.class),
|
||||
Airbend (AirbendEffect.class),
|
||||
AlterAttribute (AlterAttributeEffect.class),
|
||||
Amass (AmassEffect.class),
|
||||
Animate (AnimateEffect.class),
|
||||
@@ -81,6 +82,7 @@ public enum ApiType {
|
||||
Draft (DraftEffect.class),
|
||||
Draw (DrawEffect.class),
|
||||
EachDamage (DamageEachEffect.class),
|
||||
Earthbend (EarthbendEffect.class),
|
||||
Effect (EffectEffect.class),
|
||||
Encode (EncodeEffect.class),
|
||||
EndCombatPhase (EndCombatPhaseEffect.class),
|
||||
|
||||
@@ -49,6 +49,8 @@ public abstract class SpellAbilityEffect {
|
||||
return sa.getDescription();
|
||||
}
|
||||
|
||||
public void buildSpellAbility(final SpellAbility sa) {}
|
||||
|
||||
/**
|
||||
* Returns this effect description with needed prelude and epilogue.
|
||||
* @param params
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.Map;
|
||||
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
@@ -24,13 +23,7 @@ public class SpellApiBased extends Spell {
|
||||
// A spell is always intrinsic
|
||||
this.setIntrinsic(true);
|
||||
|
||||
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);
|
||||
}
|
||||
effect.buildSpellAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,8 +2,6 @@ package forge.game.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.ability.effects.ChangeZoneAllEffect;
|
||||
import forge.game.ability.effects.ChangeZoneEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.spellability.AbilityStatic;
|
||||
@@ -20,9 +18,7 @@ public class StaticAbilityApiBased extends AbilityStatic {
|
||||
api = api0;
|
||||
effect = api.getSpellEffect();
|
||||
|
||||
if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) {
|
||||
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
|
||||
}
|
||||
effect.buildSpellAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.event.GameEventTokenCreated;
|
||||
@@ -86,7 +85,7 @@ public class AmassEffect extends TokenEffectBase {
|
||||
}
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
|
||||
params.put("CounterType", CounterEnumType.P1P1);
|
||||
params.put("Amount", amount);
|
||||
Card tgt = activator.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), false, params);
|
||||
|
||||
|
||||
@@ -90,6 +90,16 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
c.addPerpetual(p);
|
||||
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 (perpetual) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.zone.MagicStack;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Localizer;
|
||||
|
||||
@@ -27,6 +28,13 @@ import forge.util.Localizer;
|
||||
*/
|
||||
public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void buildSpellAbility(SpellAbility sa) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.getTargetRestrictions().setZone(ZoneType.Stack);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
@@ -21,6 +22,12 @@ import forge.util.Localizer;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void buildSpellAbility(SpellAbility sa) {
|
||||
AbilityFactory.adjustChangeZoneTarget(sa.getMapParams(), sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
// TODO build Stack Description will need expansion as more cards are added
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.google.common.collect.Maps;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType;
|
||||
import forge.game.*;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
@@ -18,7 +19,6 @@ import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -34,6 +34,11 @@ import java.util.Map;
|
||||
|
||||
public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void buildSpellAbility(SpellAbility sa) {
|
||||
AbilityFactory.adjustChangeZoneTarget(sa.getMapParams(), sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
if (sa.isHidden()) {
|
||||
@@ -759,7 +764,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
StringBuilder sbPlay = new StringBuilder();
|
||||
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand+!ThisTurnEntered");
|
||||
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
||||
final StaticAbility st = eff.addStaticAbility(sbPlay.toString());
|
||||
eff.addStaticAbility(sbPlay.toString());
|
||||
eff.addRemembered(movedCard);
|
||||
addForgetOnMovedTrigger(eff, "Exile");
|
||||
addForgetOnCastTrigger(eff, "Card.IsRemembered");
|
||||
|
||||
@@ -10,8 +10,17 @@ import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class ControlSpellEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void buildSpellAbility(SpellAbility sa) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.getTargetRestrictions().setZone(ZoneType.Stack);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,12 @@ import java.util.Map;
|
||||
|
||||
|
||||
public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void buildSpellAbility(SpellAbility sa) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.getTargetRestrictions().setZone(ZoneType.Stack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
|
||||
@@ -21,6 +21,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CounterEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void buildSpellAbility(SpellAbility sa) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.getTargetRestrictions().setZone(ZoneType.Stack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -102,24 +102,29 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
int totalRemoved = 0;
|
||||
CardCollectionView srcCards;
|
||||
|
||||
if (sa.hasParam("Choices")) {
|
||||
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
||||
: ZoneType.Battlefield;
|
||||
|
||||
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
||||
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")) {
|
||||
int min = 1;
|
||||
int max = 1;
|
||||
if (sa.hasParam("ChoiceOptional")) {
|
||||
min = 0;
|
||||
max = choices.size();
|
||||
max = srcCards.size();
|
||||
}
|
||||
if (sa.hasParam("ChoiceNum")) {
|
||||
min = max = AbilityUtils.calculateAmount(source, sa.getParam("ChoiceNum"), sa);
|
||||
}
|
||||
if (choices.size() < min) {
|
||||
if (srcCards.size() < min) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,13 +133,12 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
title = title.replace(" ", " ");
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", counterType);
|
||||
srcCards = pc.chooseCardsForEffect(choices, sa, title, min, max, min == 0, params);
|
||||
srcCards = pc.chooseCardsForEffect(srcCards, sa, title, min, max, min == 0, params);
|
||||
} else {
|
||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||
if (!tgtPlayer.isInGame()) {
|
||||
continue;
|
||||
}
|
||||
// Removing energy
|
||||
if (type.equals("All")) {
|
||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
||||
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||
@@ -150,8 +154,6 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
srcCards = getTargetCards(sa);
|
||||
}
|
||||
|
||||
for (final Card tgtCard : srcCards) {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
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,6 +8,7 @@ import java.util.Map;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
@@ -142,15 +143,20 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
String image;
|
||||
if (sa.hasParam("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(), " — ", "_"),
|
||||
if (name.startsWith("Emblem")) {
|
||||
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(), " — ", "_"),
|
||||
",", ""),
|
||||
" ", "_").toLowerCase());
|
||||
" ", "_");
|
||||
image = StaticData.instance().getOtherImageKey(imageKey, hostCard.getSetCode());
|
||||
}
|
||||
} else if (sa.hasParam("Image")) {
|
||||
image = ImageKeys.getTokenKey(sa.getParam("Image"));
|
||||
} else { // use host image
|
||||
image = hostCard.getImageKey();
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class MakeCardEffect extends SpellAbilityEffect {
|
||||
if (source.hasNamedCard()) {
|
||||
names.addAll(source.getNamedCards());
|
||||
} else {
|
||||
System.err.println("Malformed MakeCard entry! - " + source.toString());
|
||||
System.err.println("Malformed MakeCard entry! - " + source);
|
||||
}
|
||||
} else {
|
||||
names.add(n);
|
||||
@@ -72,7 +72,8 @@ public class MakeCardEffect extends SpellAbilityEffect {
|
||||
cards = AbilityUtils.getDefinedCards(source, def, sa);
|
||||
}
|
||||
for (final Card c : cards) {
|
||||
names.add(c.getName());
|
||||
//get the original papercard name
|
||||
names.add(c.getPaperCard().getName());
|
||||
}
|
||||
} else if (sa.hasParam("Spellbook")) {
|
||||
faces.addAll(parseFaces(sa, "Spellbook"));
|
||||
|
||||
@@ -19,9 +19,11 @@ import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
import io.sentry.Breadcrumb;
|
||||
@@ -29,6 +31,14 @@ import io.sentry.Sentry;
|
||||
|
||||
public class ManaEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void buildSpellAbility(SpellAbility sa) {
|
||||
sa.setManaPart(new AbilityManaPart(sa, sa.getMapParams()));
|
||||
if (sa.getParent() == null) {
|
||||
sa.setUndoable(true); // will try at least
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
@@ -265,6 +275,9 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
|
||||
// Only clear express choice after mana has been produced
|
||||
abMana.clearExpressChoice();
|
||||
if (sa.isKeyword(Keyword.FIREBENDING)) {
|
||||
activator.triggerElementalBend(TriggerType.Firebend);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,14 @@ import forge.util.Localizer;
|
||||
|
||||
public class ManaReflectedEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void buildSpellAbility(SpellAbility sa) {
|
||||
sa.setManaPart(new AbilityManaPart(sa, sa.getMapParams()));
|
||||
if (sa.getParent() == null) {
|
||||
sa.setUndoable(true); // will try at least
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
|
||||
@@ -369,8 +369,8 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
List<Card> canIncrementDice = new ArrayList<>();
|
||||
for (Card c : xenosquirrels) {
|
||||
// Xenosquirrels must have a P1P1 counter on it to remove in order to modify
|
||||
Integer P1P1Counters = c.getCounters().get(CounterType.get(CounterEnumType.P1P1));
|
||||
if (P1P1Counters != null && P1P1Counters > 0 && c.canRemoveCounters(CounterType.get(CounterEnumType.P1P1))) {
|
||||
Integer P1P1Counters = c.getCounters().get(CounterEnumType.P1P1);
|
||||
if (P1P1Counters != null && P1P1Counters > 0 && c.canRemoveCounters(CounterEnumType.P1P1)) {
|
||||
canIncrementDice.add(c);
|
||||
}
|
||||
}
|
||||
@@ -399,6 +399,7 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
* @param repParams replacement effect parameters
|
||||
* @return list of final roll results after applying ignores and replacements, sorted in ascending order
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Integer> rollAction(int amount, int sides, int ignore, List<Integer> rollsResult, List<Integer> ignored, Map<Player, Integer> ignoreChosenMap, Set<Card> dicePTExchanges, Player player, Map<AbilityKey, Object> repParams) {
|
||||
|
||||
repParams.put(AbilityKey.Sides, sides);
|
||||
@@ -416,6 +417,8 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
ignoreChosenMap = (Map<Player, Integer>) repParams.get(AbilityKey.IgnoreChosen);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
List<Integer> naturalRolls = (rollsResult == null ? new ArrayList<>() : rollsResult);
|
||||
|
||||
@@ -41,6 +41,7 @@ public class TapOrUntapEffect extends SpellAbilityEffect {
|
||||
tapper = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Tapper"), sa).getFirst();
|
||||
}
|
||||
PlayerController pc = tapper.getController();
|
||||
boolean toggle = sa.hasParam("Toggle");
|
||||
|
||||
CardCollection tapped = new CardCollection();
|
||||
final Map<Player, CardCollection> untapMap = Maps.newHashMap();
|
||||
@@ -61,8 +62,12 @@ public class TapOrUntapEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
// If the effected card is controlled by the same controller of the SA, default to untap.
|
||||
boolean tap = pc.chooseBinary(sa, Localizer.getInstance().getMessage("lblTapOrUntapTarget", CardTranslation.getTranslatedName(gameCard.getName())), PlayerController.BinaryChoiceType.TapOrUntap,
|
||||
boolean tap;
|
||||
if(!toggle)
|
||||
tap = pc.chooseBinary(sa, Localizer.getInstance().getMessage("lblTapOrUntapTarget", CardTranslation.getTranslatedName(gameCard.getName())), PlayerController.BinaryChoiceType.TapOrUntap,
|
||||
!gameCard.getController().equals(tapper));
|
||||
else
|
||||
tap = !gameCard.isTapped();
|
||||
if (tap) {
|
||||
if (gameCard.tap(true, sa, tapper)) tapped.add(gameCard);
|
||||
} else if (gameCard.untap()) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
@@ -37,7 +38,7 @@ public class TimeTravelEffect extends SpellAbilityEffect {
|
||||
|
||||
PlayerController pc = activator.getController();
|
||||
|
||||
final CounterEnumType counterType = CounterEnumType.TIME;
|
||||
final CounterType counterType = CounterEnumType.TIME;
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
FCollection<Card> list = new FCollection<>();
|
||||
|
||||
@@ -1051,7 +1051,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
// it was always created), adjust threshold based on its existence.
|
||||
int threshold = states.containsKey(CardStateName.FaceDown) ? 2 : 1;
|
||||
|
||||
int numStates = states.keySet().size();
|
||||
int numStates = states.size();
|
||||
|
||||
return numStates > threshold;
|
||||
}
|
||||
@@ -2762,7 +2762,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")
|
||||
|| keyword.startsWith("Encore") || keyword.startsWith("Mutate") || keyword.startsWith("Dungeon")
|
||||
|| keyword.startsWith("Class") || keyword.startsWith("Blitz") || keyword.startsWith("Web-slinging")
|
||||
|| keyword.startsWith("Specialize") || keyword.equals("Ravenous")
|
||||
|| keyword.startsWith("Specialize") || keyword.equals("Ravenous") || keyword.startsWith("Firebending")
|
||||
|| keyword.equals("For Mirrodin") || keyword.equals("Job select") || keyword.startsWith("Craft")
|
||||
|| keyword.startsWith("Landwalk") || keyword.startsWith("Visit") || keyword.startsWith("Mobilize")
|
||||
|| keyword.startsWith("Station") || keyword.startsWith("Warp") || keyword.startsWith("Devour")) {
|
||||
@@ -3219,6 +3219,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getEffectSource().getName());
|
||||
}
|
||||
|
||||
// Ensure no more escaped linebreak are present
|
||||
desc = desc.replace("\\r", "\r")
|
||||
.replace("\\n", "\n");
|
||||
|
||||
return desc.trim();
|
||||
}
|
||||
|
||||
@@ -3557,14 +3561,23 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
if (!getStaticAbilities().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!getReplacementEffects().isEmpty()) {
|
||||
if (!getReplacementEffects().isEmpty()
|
||||
&& (getReplacementEffects().size() > 1 || !isSaga() || hasKeyword(Keyword.READ_AHEAD))) {
|
||||
return false;
|
||||
}
|
||||
if (!getTriggers().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (SpellAbility sa : getSpellAbilities()) {
|
||||
if (!(sa instanceof SpellPermanent && sa.isBasicSpell()) && !sa.isMorphUp() && !sa.isDisguiseUp()) {
|
||||
// morph up and disguise up are not part of the card
|
||||
if (sa.isMorphUp() || sa.isDisguiseUp()) {
|
||||
continue;
|
||||
}
|
||||
// while Adventure and Omen are part of Secondary
|
||||
if ((sa.isAdventure() || sa.isOmen()) && !getCurrentStateName().equals(sa.getCardState())) {
|
||||
continue;
|
||||
}
|
||||
if (!(sa instanceof SpellPermanent && sa.isBasicSpell())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3601,13 +3614,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (hasState(CardStateName.Secondary) && state.getStateName() == CardStateName.Original) {
|
||||
// Adventure and Omen may only be cast not from Battlefield
|
||||
if (hasState(CardStateName.Secondary) && state.getStateName() == CardStateName.Original) {
|
||||
for (SpellAbility sa : getState(CardStateName.Secondary).getSpellAbilities()) {
|
||||
if (mana == null || mana == sa.isManaAbility()) {
|
||||
list.add(sa);
|
||||
}
|
||||
for (SpellAbility sa : getState(CardStateName.Secondary).getSpellAbilities()) {
|
||||
if (mana == null || mana == sa.isManaAbility()) {
|
||||
list.add(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6431,10 +6442,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
DamageType damageType = DamageType.Normal;
|
||||
if (isPlaneswalker()) { // 120.3c
|
||||
subtractCounter(CounterType.get(CounterEnumType.LOYALTY), damageIn, null, true);
|
||||
subtractCounter(CounterEnumType.LOYALTY, damageIn, null, true);
|
||||
}
|
||||
if (isBattle()) {
|
||||
subtractCounter(CounterType.get(CounterEnumType.DEFENSE), damageIn, null, true);
|
||||
subtractCounter(CounterEnumType.DEFENSE, damageIn, null, true);
|
||||
}
|
||||
if (isCreature()) {
|
||||
if (source.isWitherDamage()) { // 120.3d
|
||||
|
||||
@@ -198,7 +198,9 @@ public class CardFactory {
|
||||
if (c.hasAlternateState()) {
|
||||
if (c.isFlipCard()) {
|
||||
c.setState(CardStateName.Flipped, false);
|
||||
c.setImageKey(cp.getImageKey(true));
|
||||
// set the imagekey altstate to false since the rotated image is handled by graphics renderer
|
||||
// setting this to true will download the original image with different name.
|
||||
c.setImageKey(cp.getImageKey(false));
|
||||
}
|
||||
else if (c.isDoubleFaced() && cardRules != null) {
|
||||
c.setState(cardRules.getSplitType().getChangedStateName(), false);
|
||||
|
||||
@@ -1170,6 +1170,29 @@ public class CardFactoryUtil {
|
||||
removeCounterSA.setIntrinsic(intrinsic);
|
||||
trigger.setOverridingAbility(removeCounterSA);
|
||||
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.startsWith("Firebending")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String n = k[1];
|
||||
|
||||
StringBuilder desc = new StringBuilder("Firebending ");
|
||||
desc.append(n);
|
||||
if (k.length > 2) {
|
||||
desc.append(" ").append(k[2]);
|
||||
}
|
||||
|
||||
desc.append(" (").append(inst.getReminderText()).append(")");
|
||||
|
||||
final String trigStr = "Mode$ Attacks | ValidCard$ Card.Self | TriggerDescription$ " + desc.toString();
|
||||
|
||||
final String manaStr = "DB$ Mana | Defined$ You | CombatMana$ True | Produced$ R | Amount$ " + n;
|
||||
|
||||
final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
|
||||
SpellAbility manaSA = AbilityFactory.getAbility(manaStr, card);
|
||||
|
||||
manaSA.setIntrinsic(intrinsic);
|
||||
trigger.setOverridingAbility(manaSA);
|
||||
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.equals("Flanking")) {
|
||||
final StringBuilder trigFlanking = new StringBuilder(
|
||||
@@ -2545,7 +2568,7 @@ public class CardFactoryUtil {
|
||||
} else if (keyword.equals("Sunburst")) {
|
||||
// Rule 702.43a If this object is entering the battlefield as a creature,
|
||||
// ignoring any type-changing effects that would affect it
|
||||
CounterType t = CounterType.get(host.isCreature() ? CounterEnumType.P1P1 : CounterEnumType.CHARGE);
|
||||
CounterType t = host.isCreature() ? CounterEnumType.P1P1 : CounterEnumType.CHARGE;
|
||||
|
||||
StringBuilder sb = new StringBuilder("etbCounter:");
|
||||
sb.append(t).append(":Sunburst:no Condition:");
|
||||
|
||||
@@ -213,16 +213,10 @@ public final class CardPredicates {
|
||||
public static Predicate<Card> hasCounter(final CounterType type) {
|
||||
return hasCounter(type, 1);
|
||||
}
|
||||
public static Predicate<Card> hasCounter(final CounterEnumType type) {
|
||||
return hasCounter(type, 1);
|
||||
}
|
||||
|
||||
public static Predicate<Card> hasCounter(final CounterType type, final int n) {
|
||||
return c -> c.getCounters(type) >= n;
|
||||
}
|
||||
public static Predicate<Card> hasCounter(final CounterEnumType type, final int n) {
|
||||
return hasCounter(CounterType.get(type), n);
|
||||
}
|
||||
|
||||
public static Predicate<Card> hasLessCounter(final CounterType type, final int n) {
|
||||
return c -> {
|
||||
@@ -230,16 +224,10 @@ public final class CardPredicates {
|
||||
return x > 0 && x <= n;
|
||||
};
|
||||
}
|
||||
public static Predicate<Card> hasLessCounter(final CounterEnumType type, final int n) {
|
||||
return hasLessCounter(CounterType.get(type), n);
|
||||
}
|
||||
|
||||
public static Predicate<Card> canReceiveCounters(final CounterType counter) {
|
||||
return c -> c.canReceiveCounters(counter);
|
||||
}
|
||||
public static Predicate<Card> canReceiveCounters(final CounterEnumType counter) {
|
||||
return canReceiveCounters(CounterType.get(counter));
|
||||
}
|
||||
|
||||
public static Predicate<Card> hasGreaterPowerThan(final int minPower) {
|
||||
return c -> c.getNetPower() > minPower;
|
||||
@@ -248,9 +236,6 @@ public final class CardPredicates {
|
||||
public static Comparator<Card> compareByCounterType(final CounterType type) {
|
||||
return Comparator.comparingInt(arg0 -> arg0.getCounters(type));
|
||||
}
|
||||
public static Comparator<Card> compareByCounterType(final CounterEnumType type) {
|
||||
return compareByCounterType(CounterType.get(type));
|
||||
}
|
||||
|
||||
public static Predicate<Card> hasSVar(final String name) {
|
||||
return c -> c.hasSVar(name);
|
||||
|
||||
@@ -605,18 +605,18 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
result.add(loyaltyRep);
|
||||
}
|
||||
if (type.isBattle()) {
|
||||
// TODO This is currently breaking for Battle/Defense
|
||||
// Going to script the cards to work but ideally it would happen here
|
||||
if (defenseRep == null) {
|
||||
defenseRep = CardFactoryUtil.makeEtbCounter("etbCounter:DEFENSE:" + this.baseDefense, this, true);
|
||||
}
|
||||
result.add(defenseRep);
|
||||
|
||||
// TODO add Siege "Choose a player to protect it"
|
||||
}
|
||||
|
||||
card.updateReplacementEffects(result, this);
|
||||
|
||||
// below are global rules
|
||||
if (type.hasSubtype("Saga") && !hasKeyword(Keyword.READ_AHEAD)) {
|
||||
if (sagaRep == null) {
|
||||
sagaRep = CardFactoryUtil.makeEtbCounter("etbCounter:LORE:1", this, true);
|
||||
sagaRep = CardFactoryUtil.makeEtbCounter("etbCounter:LORE:1", this, false);
|
||||
}
|
||||
result.add(sagaRep);
|
||||
}
|
||||
@@ -633,7 +633,6 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
result.add(omenRep);
|
||||
}
|
||||
|
||||
card.updateReplacementEffects(result, this);
|
||||
return result;
|
||||
}
|
||||
public boolean addReplacementEffect(final ReplacementEffect replacementEffect) {
|
||||
@@ -750,11 +749,21 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
triggers.add(tr.copy(card, lki));
|
||||
}
|
||||
}
|
||||
ReplacementEffect runRE = null;
|
||||
if (ctb instanceof SpellAbility sp && sp.isReplacementAbility()
|
||||
&& source.getCard().equals(ctb.getHostCard())) {
|
||||
runRE = sp.getReplacementEffect();
|
||||
}
|
||||
|
||||
replacementEffects.clear();
|
||||
for (ReplacementEffect re : source.replacementEffects) {
|
||||
if (re.isIntrinsic()) {
|
||||
replacementEffects.add(re.copy(card, lki));
|
||||
ReplacementEffect reCopy = re.copy(card, lki);
|
||||
if (re.equals(runRE) && runRE.hasRun()) {
|
||||
// CR 208.2b prevent loop from card copying itself
|
||||
reCopy.setHasRun(true);
|
||||
}
|
||||
replacementEffects.add(reCopy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package forge.game.card;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
@@ -8,74 +7,45 @@ import forge.game.trigger.Trigger;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CardTraitChanges implements Cloneable {
|
||||
|
||||
private List<Trigger> triggers = Lists.newArrayList();
|
||||
private List<ReplacementEffect> replacements = Lists.newArrayList();
|
||||
private List<SpellAbility> abilities = Lists.newArrayList();
|
||||
private List<StaticAbility> staticAbilities = Lists.newArrayList();
|
||||
|
||||
private List<SpellAbility> removedAbilities = Lists.newArrayList();
|
||||
|
||||
private boolean removeAll = false;
|
||||
private boolean removeNonMana = false;
|
||||
|
||||
public CardTraitChanges(Collection<SpellAbility> spells, Collection<SpellAbility> removedAbilities,
|
||||
Collection<Trigger> trigger, Collection<ReplacementEffect> res, Collection<StaticAbility> st,
|
||||
boolean removeAll, boolean removeNonMana) {
|
||||
if (spells != null) {
|
||||
this.abilities.addAll(spells);
|
||||
}
|
||||
if (removedAbilities != null) {
|
||||
this.removedAbilities.addAll(removedAbilities);
|
||||
}
|
||||
if (trigger != null) {
|
||||
this.triggers.addAll(trigger);
|
||||
}
|
||||
if (res != null) {
|
||||
this.replacements.addAll(res);
|
||||
}
|
||||
if (st != null) {
|
||||
this.staticAbilities.addAll(st);
|
||||
}
|
||||
|
||||
this.removeAll |= removeAll;
|
||||
this.removeNonMana |= removeNonMana;
|
||||
}
|
||||
public record CardTraitChanges(Collection<SpellAbility> abilities, Collection<SpellAbility> removedAbilities,
|
||||
Collection<Trigger> triggers, Collection<ReplacementEffect> replacements, Collection<StaticAbility> staticAbilities,
|
||||
boolean removeAll, boolean removeNonMana) {
|
||||
|
||||
/**
|
||||
* @return the triggers
|
||||
*/
|
||||
public Collection<Trigger> getTriggers() {
|
||||
return triggers;
|
||||
return Objects.requireNonNullElse(triggers, List.of());
|
||||
}
|
||||
/**
|
||||
* @return the replacements
|
||||
*/
|
||||
public Collection<ReplacementEffect> getReplacements() {
|
||||
return replacements;
|
||||
return Objects.requireNonNullElse(replacements, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the abilities
|
||||
*/
|
||||
public Collection<SpellAbility> getAbilities() {
|
||||
return abilities;
|
||||
return Objects.requireNonNullElse(abilities, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the abilities
|
||||
*/
|
||||
public Collection<SpellAbility> getRemovedAbilities() {
|
||||
return removedAbilities;
|
||||
return Objects.requireNonNullElse(removedAbilities, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the staticAbilities
|
||||
*/
|
||||
public Collection<StaticAbility> getStaticAbilities() {
|
||||
return staticAbilities;
|
||||
return Objects.requireNonNullElse(staticAbilities, List.of());
|
||||
}
|
||||
|
||||
public boolean isRemoveAll() {
|
||||
@@ -87,53 +57,30 @@ public class CardTraitChanges implements Cloneable {
|
||||
}
|
||||
|
||||
public CardTraitChanges copy(Card host, boolean lki) {
|
||||
try {
|
||||
CardTraitChanges result = (CardTraitChanges) super.clone();
|
||||
|
||||
result.abilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
result.abilities.add(sa.copy(host, lki));
|
||||
}
|
||||
result.removedAbilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : this.removedAbilities) {
|
||||
result.removedAbilities.add(sa.copy(host, lki));
|
||||
}
|
||||
|
||||
result.triggers = Lists.newArrayList();
|
||||
for (Trigger tr : this.triggers) {
|
||||
result.triggers.add(tr.copy(host, lki));
|
||||
}
|
||||
|
||||
result.replacements = Lists.newArrayList();
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
result.replacements.add(re.copy(host, lki));
|
||||
}
|
||||
|
||||
result.staticAbilities = Lists.newArrayList();
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
result.staticAbilities.add(sa.copy(host, lki));
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("CardTraitChanges : clone() error", ex);
|
||||
}
|
||||
return new CardTraitChanges(
|
||||
this.getAbilities().stream().map(sa -> sa.copy(host, lki)).collect(Collectors.toList()),
|
||||
this.getRemovedAbilities().stream().map(sa -> sa.copy(host, lki)).collect(Collectors.toList()),
|
||||
this.getTriggers().stream().map(tr -> tr.copy(host, lki)).collect(Collectors.toList()),
|
||||
this.getReplacements().stream().map(tr -> tr.copy(host, lki)).collect(Collectors.toList()),
|
||||
this.getStaticAbilities().stream().map(st -> st.copy(host, lki)).collect(Collectors.toList()),
|
||||
removeAll, removeNonMana
|
||||
);
|
||||
}
|
||||
|
||||
public void changeText() {
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
for (SpellAbility sa : this.getAbilities()) {
|
||||
sa.changeText();
|
||||
}
|
||||
|
||||
for (Trigger tr : this.triggers) {
|
||||
for (Trigger tr : this.getTriggers()) {
|
||||
tr.changeText();
|
||||
}
|
||||
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
for (ReplacementEffect re : this.getReplacements()) {
|
||||
re.changeText();
|
||||
}
|
||||
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
for (StaticAbility sa : this.getStaticAbilities()) {
|
||||
sa.changeText();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,9 +142,8 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
|
||||
public boolean isFlipped() {
|
||||
return get(TrackableProperty.Flipped); // getCurrentState().getState() == CardStateName.Flipped;
|
||||
return get(TrackableProperty.Flipped);
|
||||
}
|
||||
|
||||
public boolean isSplitCard() {
|
||||
return get(TrackableProperty.SplitCard);
|
||||
}
|
||||
@@ -939,7 +938,9 @@ public class CardView extends GameEntityView {
|
||||
sb.append("\r\n\r\nMerged Cards: ").append(mergedCards);
|
||||
}
|
||||
|
||||
return sb.toString().trim();
|
||||
return sb.toString().trim()
|
||||
.replace("\\r", "\r")
|
||||
.replace("\\n", "\n");
|
||||
}
|
||||
|
||||
public CardStateView getCurrentState() {
|
||||
@@ -1027,6 +1028,7 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.Cloned, c.isCloned());
|
||||
set(TrackableProperty.SplitCard, isSplitCard);
|
||||
set(TrackableProperty.FlipCard, c.isFlipCard());
|
||||
set(TrackableProperty.Flipped, c.getCurrentStateName() == CardStateName.Flipped);
|
||||
set(TrackableProperty.Facedown, c.isFaceDown());
|
||||
set(TrackableProperty.Foretold, c.isForetold());
|
||||
set(TrackableProperty.Secondary, c.hasState(CardStateName.Secondary));
|
||||
@@ -1104,7 +1106,7 @@ public class CardView extends GameEntityView {
|
||||
currentState.getView().setOriginalColors(c); //set original Colors
|
||||
|
||||
currentStateView.updateAttractionLights(currentState);
|
||||
currentStateView.updateHasPrintedPT(c.getRules() != null && c.getRules().hasPrintedPT());
|
||||
currentStateView.updateHasPrintedPT((currentStateView.isVehicle() || currentStateView.isSpaceCraft()) && c.getRules() != null && c.getRules().hasPrintedPT());
|
||||
|
||||
CardState alternateState = isSplitCard && isFaceDown() ? c.getState(CardStateName.RightSplit) : c.getAlternateState();
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.Locale;
|
||||
* @author Clemens Koza
|
||||
* @version V0.0 17.02.2010
|
||||
*/
|
||||
public enum CounterEnumType {
|
||||
public enum CounterEnumType implements CounterType {
|
||||
|
||||
M1M1("-1/-1", "-1/-1", 255, 110, 106),
|
||||
P1P1("+1/+1", "+1/+1", 96, 226, 23),
|
||||
@@ -169,6 +169,8 @@ public enum CounterEnumType {
|
||||
|
||||
FINALITY("FINAL", 255, 255, 255),
|
||||
|
||||
FIRE("FIRE", 240, 30, 35),
|
||||
|
||||
FLAME("FLAME", 255, 143, 43),
|
||||
|
||||
FLAVOR("FLAVOR", 208, 152, 97), ///adventure only
|
||||
@@ -553,4 +555,14 @@ public enum CounterEnumType {
|
||||
|
||||
public static final ImmutableList<CounterEnumType> values = ImmutableList.copyOf(values());
|
||||
|
||||
@Override
|
||||
public boolean is(CounterEnumType eType) {
|
||||
return this == eType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKeywordCounter() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package forge.game.card;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
public record CounterKeywordType(String keyword) implements CounterType {
|
||||
|
||||
// Rule 122.1b
|
||||
static ImmutableList<String> keywordCounter = ImmutableList.of(
|
||||
"Flying", "First Strike", "Double Strike", "Deathtouch", "Decayed", "Exalted", "Haste", "Hexproof",
|
||||
"Indestructible", "Lifelink", "Menace", "Reach", "Shadow", "Trample", "Vigilance");
|
||||
private static Map<String, CounterKeywordType> sMap = Maps.newHashMap();
|
||||
|
||||
|
||||
public static CounterKeywordType get(String s) {
|
||||
if (!sMap.containsKey(s)) {
|
||||
sMap.put(s, new CounterKeywordType(s));
|
||||
}
|
||||
return sMap.get(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return getKeywordDescription();
|
||||
}
|
||||
|
||||
public String getCounterOnCardDisplayName() {
|
||||
return getKeywordDescription();
|
||||
}
|
||||
|
||||
private String getKeywordDescription() {
|
||||
if (keyword.startsWith("Hexproof:")) {
|
||||
final String[] k = keyword.split(":");
|
||||
return "Hexproof from " + k[2];
|
||||
}
|
||||
if (keyword.startsWith("Trample:")) {
|
||||
return "Trample over Planeswalkers";
|
||||
}
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public boolean is(CounterEnumType eType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isKeywordCounter() {
|
||||
if (keyword.startsWith("Hexproof:")) {
|
||||
return true;
|
||||
}
|
||||
if (keyword.startsWith("Trample:")) {
|
||||
return true;
|
||||
}
|
||||
return keywordCounter.contains(keyword);
|
||||
}
|
||||
|
||||
|
||||
public int getRed() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
public int getGreen() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
public int getBlue() {
|
||||
return 255;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +1,31 @@
|
||||
package forge.game.card;
|
||||
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Ordering;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class CounterType implements Comparable<CounterType>, Serializable {
|
||||
private static final long serialVersionUID = -7575835723159144478L;
|
||||
|
||||
private CounterEnumType eVal = null;
|
||||
private String sVal = null;
|
||||
|
||||
// Rule 122.1b
|
||||
static ImmutableList<String> keywordCounter = ImmutableList.of(
|
||||
"Flying", "First Strike", "Double Strike", "Deathtouch", "Decayed", "Exalted", "Haste", "Hexproof",
|
||||
"Indestructible", "Lifelink", "Menace", "Reach", "Shadow", "Trample", "Vigilance");
|
||||
|
||||
private static Map<CounterEnumType, CounterType> eMap = Maps.newEnumMap(CounterEnumType.class);
|
||||
private static Map<String, CounterType> sMap = Maps.newHashMap();
|
||||
|
||||
private CounterType(CounterEnumType e, String s) {
|
||||
this.eVal = e;
|
||||
this.sVal = s;
|
||||
}
|
||||
|
||||
public static CounterType get(CounterEnumType e) {
|
||||
if (!eMap.containsKey(e)) {
|
||||
eMap.put(e, new CounterType(e, null));
|
||||
}
|
||||
return eMap.get(e);
|
||||
}
|
||||
|
||||
public static CounterType get(String s) {
|
||||
if (!sMap.containsKey(s)) {
|
||||
sMap.put(s, new CounterType(null, s));
|
||||
}
|
||||
return sMap.get(s);
|
||||
}
|
||||
public interface CounterType extends Serializable {
|
||||
|
||||
public static CounterType getType(String name) {
|
||||
if ("Any".equalsIgnoreCase(name)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return get(CounterEnumType.getType(name));
|
||||
return CounterEnumType.getType(name);
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
return get(name);
|
||||
return CounterKeywordType.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName();
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(eVal, sVal);
|
||||
}
|
||||
public String getCounterOnCardDisplayName();
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
CounterType rhs = (CounterType) obj;
|
||||
return new EqualsBuilder()
|
||||
.append(eVal, rhs.eVal)
|
||||
.append(sVal, rhs.sVal)
|
||||
.isEquals();
|
||||
}
|
||||
public boolean is(CounterEnumType eType);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return eVal != null ? eVal.toString() : sVal;
|
||||
}
|
||||
public boolean isKeywordCounter();
|
||||
|
||||
public String getName() {
|
||||
return eVal != null ? eVal.getName() : getKeywordDescription();
|
||||
}
|
||||
public int getRed();
|
||||
|
||||
public String getCounterOnCardDisplayName() {
|
||||
return eVal != null ? eVal.getCounterOnCardDisplayName() : getKeywordDescription();
|
||||
}
|
||||
public int getGreen();
|
||||
|
||||
private String getKeywordDescription() {
|
||||
if (sVal.startsWith("Hexproof:")) {
|
||||
final String[] k = sVal.split(":");
|
||||
return "Hexproof from " + k[2];
|
||||
}
|
||||
if (sVal.startsWith("Trample:")) {
|
||||
return "Trample over Planeswalkers";
|
||||
}
|
||||
return sVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CounterType o) {
|
||||
return ComparisonChain.start()
|
||||
.compare(eVal, o.eVal, Ordering.natural().nullsLast())
|
||||
.compare(sVal, o.sVal, Ordering.natural().nullsLast())
|
||||
.result();
|
||||
}
|
||||
|
||||
public boolean is(CounterEnumType eType) {
|
||||
return eVal == eType;
|
||||
}
|
||||
|
||||
public boolean isKeywordCounter() {
|
||||
if (eVal != null) {
|
||||
return false;
|
||||
}
|
||||
if (sVal.startsWith("Hexproof:")) {
|
||||
return true;
|
||||
}
|
||||
if (sVal.startsWith("Trample:")) {
|
||||
return true;
|
||||
}
|
||||
return keywordCounter.contains(sVal);
|
||||
}
|
||||
|
||||
public int getRed() {
|
||||
return eVal != null ? eVal.getRed() : 255;
|
||||
}
|
||||
|
||||
public int getGreen() {
|
||||
return eVal != null ? eVal.getGreen() : 255;
|
||||
}
|
||||
|
||||
public int getBlue() {
|
||||
return eVal != null ? eVal.getBlue() : 255;
|
||||
}
|
||||
public int getBlue();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package forge.game.card.perpetual;
|
||||
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
|
||||
public record PerpetualManaCost(long timestamp, ManaCost manaCost) implements PerpetualInterface {
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyEffect(Card c) {
|
||||
c.addChangedManaCost(manaCost, timestamp, (long) 0);
|
||||
|
||||
c.updateManaCostForView();
|
||||
|
||||
if (c.getFirstSpellAbility() != null) {
|
||||
Cost cost = c.getFirstSpellAbility().getPayCosts().copyWithDefinedMana(manaCost);
|
||||
c.getFirstSpellAbility().setPayCosts(cost);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -621,8 +621,11 @@ public class Cost implements Serializable {
|
||||
}
|
||||
|
||||
public final Cost copyWithDefinedMana(String manaCost) {
|
||||
return copyWithDefinedMana(new ManaCost(new ManaCostParser(manaCost)));
|
||||
}
|
||||
public final Cost copyWithDefinedMana(ManaCost manaCost) {
|
||||
Cost toRet = copyWithNoMana();
|
||||
toRet.costParts.add(new CostPartMana(new ManaCost(new ManaCostParser(manaCost)), null));
|
||||
toRet.costParts.add(new CostPartMana(manaCost, null));
|
||||
toRet.cacheTapCost();
|
||||
return toRet;
|
||||
}
|
||||
@@ -994,9 +997,9 @@ public class Cost implements Serializable {
|
||||
Integer counters = otherAmount - part.convertAmount();
|
||||
// the cost can turn positive if multiple Carth raise it
|
||||
if (counters < 0) {
|
||||
costParts.add(new CostPutCounter(String.valueOf(counters *-1), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription()));
|
||||
costParts.add(new CostPutCounter(String.valueOf(counters *-1), CounterEnumType.LOYALTY, part.getType(), part.getTypeDescription()));
|
||||
} else {
|
||||
costParts.add(new CostRemoveCounter(String.valueOf(counters), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription(), Lists.newArrayList(ZoneType.Battlefield) , false));
|
||||
costParts.add(new CostRemoveCounter(String.valueOf(counters), CounterEnumType.LOYALTY, part.getType(), part.getTypeDescription(), Lists.newArrayList(ZoneType.Battlefield) , false));
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
|
||||
@@ -67,7 +67,7 @@ public class CostExileFromStack extends CostPart {
|
||||
final String desc = this.getTypeDescription() == null ? this.getType() : this.getTypeDescription();
|
||||
sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), desc));
|
||||
|
||||
sb.append("from stack");
|
||||
sb.append(" from stack");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -81,7 +80,7 @@ public class CostUntap extends CostPart {
|
||||
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
|
||||
final Card source = ability.getHostCard();
|
||||
return source.isTapped() && !source.isAbilitySick() &&
|
||||
(source.getCounters(CounterEnumType.STUN) == 0 || source.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
|
||||
(source.getCounters(CounterEnumType.STUN) == 0 || source.canRemoveCounters(CounterEnumType.STUN));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -86,7 +86,7 @@ public class CostUntapType extends CostPartWithList {
|
||||
if (!canUntapSource) {
|
||||
typeList.remove(source);
|
||||
}
|
||||
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
|
||||
typeList = CardLists.filter(typeList, CardPredicates.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterEnumType.STUN));
|
||||
|
||||
final int amount = this.getAbilityAmount(ability);
|
||||
return (typeList.size() != 0) && (typeList.size() >= amount);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package forge.game.event;
|
||||
|
||||
public abstract class Event {
|
||||
public interface Event {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package forge.game.event;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public enum EventValueChangeType {
|
||||
Added,
|
||||
Removed,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package forge.game.event;
|
||||
|
||||
public abstract class GameEvent extends Event {
|
||||
public interface GameEvent extends Event {
|
||||
|
||||
public abstract <T> T visit(IGameEventVisitor<T> visitor);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,7 @@ import com.google.common.collect.Multimap;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class GameEventAnteCardsSelected extends GameEvent {
|
||||
public final Multimap<Player, Card> cards;
|
||||
public GameEventAnteCardsSelected(Multimap<Player, Card> list) {
|
||||
cards = list;
|
||||
}
|
||||
public record GameEventAnteCardsSelected(Multimap<Player, Card> cards) implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
|
||||
@@ -6,27 +6,21 @@ import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class GameEventAttackersDeclared extends GameEvent {
|
||||
|
||||
public final Player player;
|
||||
public final Multimap<GameEntity, Card> attackersMap;
|
||||
|
||||
public GameEventAttackersDeclared(Player playerTurn, Multimap<GameEntity, Card> attackersMap) {
|
||||
this.player = playerTurn;
|
||||
this.attackersMap = attackersMap;
|
||||
}
|
||||
public record GameEventAttackersDeclared(Player player, Multimap<GameEntity, Card> attackersMap) implements GameEvent {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.event.GameEvent#visit(forge.game.event.IGameEventVisitor)
|
||||
*/
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
// TODO Auto-generated method stub
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + player + " declared attackers: " + attackersMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,23 +12,10 @@ import forge.util.Lang;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.maps.MapOfLists;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class GameEventBlockersDeclared extends GameEvent {
|
||||
|
||||
public final Map<GameEntity, MapOfLists<Card, Card>> blockers;
|
||||
public final Player defendingPlayer;
|
||||
|
||||
public GameEventBlockersDeclared(Player who, Map<GameEntity, MapOfLists<Card, Card>> blockers) {
|
||||
this.blockers = blockers;
|
||||
defendingPlayer = who;
|
||||
}
|
||||
public record GameEventBlockersDeclared(Player defendingPlayer, Map<GameEntity, MapOfLists<Card, Card>> blockers) implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
// TODO Auto-generated method stub
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,20 +3,18 @@ package forge.game.event;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
|
||||
public class GameEventCardAttachment extends GameEvent {
|
||||
|
||||
public final Card equipment;
|
||||
public final GameEntity newTarget; // can enchant player, I'm ssaving a class to enchants - it could be incorrect.
|
||||
public final GameEntity oldEntiy;
|
||||
|
||||
public GameEventCardAttachment(Card attachment, GameEntity formerEntity, GameEntity newEntity) {
|
||||
this.equipment = attachment;
|
||||
this.newTarget = newEntity;
|
||||
this.oldEntiy = formerEntity;
|
||||
}
|
||||
public record GameEventCardAttachment(Card equipment, GameEntity newTarget, GameEntity oldEntity) implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return newTarget == null ? "Detached " + equipment + " from " + oldEntity : "Attached " + equipment + (oldEntity == null ? "" : " from " + oldEntity) + " to " + newTarget;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,7 @@ import forge.game.card.Card;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
public class GameEventCardChangeZone extends GameEvent {
|
||||
|
||||
public final Card card;
|
||||
public final Zone from;
|
||||
public final Zone to;
|
||||
|
||||
public GameEventCardChangeZone(Card c, Zone zoneFrom, Zone zoneTo) {
|
||||
card = c;
|
||||
from = zoneFrom;
|
||||
to = zoneTo;
|
||||
}
|
||||
public record GameEventCardChangeZone(Card card, Zone from, Zone to) implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
|
||||
@@ -3,21 +3,17 @@ package forge.game.event;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
|
||||
public class GameEventCardCounters extends GameEvent {
|
||||
public final Card card;
|
||||
public final CounterType type;
|
||||
public final int oldValue;
|
||||
public final int newValue;
|
||||
|
||||
public GameEventCardCounters(Card card, CounterType counterType, int old, int newValue) {
|
||||
this.card = card;
|
||||
type = counterType;
|
||||
this.oldValue = old;
|
||||
this.newValue = newValue;
|
||||
}
|
||||
|
||||
public record GameEventCardCounters(Card card, CounterType type, int oldValue, int newValue) implements GameEvent {
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + card + " " + type + " counters: " + oldValue + " -> " + newValue;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package forge.game.event;
|
||||
|
||||
import forge.game.card.Card;
|
||||
|
||||
public class GameEventCardDamaged extends GameEvent {
|
||||
public record GameEventCardDamaged(Card card, Card source, int amount, DamageType type) implements GameEvent {
|
||||
|
||||
public enum DamageType {
|
||||
Normal,
|
||||
@@ -11,21 +11,16 @@ public class GameEventCardDamaged extends GameEvent {
|
||||
LoyaltyLoss
|
||||
}
|
||||
|
||||
public final Card card;
|
||||
public final Card source;
|
||||
public final int amount;
|
||||
public final DamageType type;
|
||||
|
||||
public GameEventCardDamaged(Card card, Card src, int damageToAdd, DamageType damageType) {
|
||||
this.card = card;
|
||||
source = src;
|
||||
amount = damageToAdd;
|
||||
type = damageType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + source + " dealt " + amount + " " + type + " damage to " + card;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package forge.game.event;
|
||||
|
||||
public class GameEventCardDestroyed extends GameEvent {
|
||||
public record GameEventCardDestroyed() implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Card destroyed";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,7 @@ package forge.game.event;
|
||||
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class GameEventCardForetold extends GameEvent {
|
||||
public final Player activatingPlayer;
|
||||
|
||||
public GameEventCardForetold(Player player) {
|
||||
activatingPlayer = player;
|
||||
}
|
||||
public record GameEventCardForetold(Player activatingPlayer) implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
|
||||
@@ -2,21 +2,7 @@ package forge.game.event;
|
||||
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class GameEventCardModeChosen extends GameEvent {
|
||||
|
||||
public final Player player;
|
||||
public final String cardName;
|
||||
public final String mode;
|
||||
public final boolean log;
|
||||
public final boolean random;
|
||||
|
||||
public GameEventCardModeChosen(Player player, String cardName, String mode, boolean log, boolean random) {
|
||||
this.player = player;
|
||||
this.cardName = cardName;
|
||||
this.mode = mode;
|
||||
this.log = log;
|
||||
this.random = random;
|
||||
}
|
||||
public record GameEventCardModeChosen(Player player, String cardName, String mode, boolean log, boolean random) implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
|
||||
@@ -2,19 +2,7 @@ package forge.game.event;
|
||||
|
||||
import forge.game.card.Card;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class GameEventCardPhased extends GameEvent {
|
||||
|
||||
public final Card card;
|
||||
public final boolean phaseState;
|
||||
|
||||
public GameEventCardPhased(Card card, boolean state) {
|
||||
this.card = card;
|
||||
phaseState = state;
|
||||
}
|
||||
public record GameEventCardPhased(Card card, boolean phaseState) implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
|
||||
@@ -3,16 +3,7 @@ package forge.game.event;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class GameEventCardPlotted extends GameEvent {
|
||||
|
||||
public final Card card;
|
||||
|
||||
public final Player activatingPlayer;
|
||||
|
||||
public GameEventCardPlotted(Card card, Player player) {
|
||||
this.card = card;
|
||||
activatingPlayer = player;
|
||||
}
|
||||
public record GameEventCardPlotted(Card card, Player activatingPlayer) implements GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user