mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
Compare commits
302 Commits
forge-1.6.
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
712e7dbeb8 | ||
|
|
22571acd0c | ||
|
|
eba8d0bd80 | ||
|
|
4b52dea65a | ||
|
|
a5eeaae0d0 | ||
|
|
0d85eb6b90 | ||
|
|
3abb226b5c | ||
|
|
d38fdfb291 | ||
|
|
a63361302d | ||
|
|
069dbce000 | ||
|
|
5970c575c8 | ||
|
|
6ba659ff55 | ||
|
|
2c9f0e3c7d | ||
|
|
06359b9d06 | ||
|
|
7f31fd5092 | ||
|
|
01cdd82cc5 | ||
|
|
c6094cf3ee | ||
|
|
2ba7da0486 | ||
|
|
dcc0295b4b | ||
|
|
8632b0ffec | ||
|
|
bc0b69857a | ||
|
|
198d48fd84 | ||
|
|
e85d6bebd0 | ||
|
|
0679d9a30f | ||
|
|
b0584d4499 | ||
|
|
51bfc37a27 | ||
|
|
c979b27653 | ||
|
|
184f0fad99 | ||
|
|
f9b88c2738 | ||
|
|
f47a679dda | ||
|
|
e5e56b5260 | ||
|
|
c62f6b41d2 | ||
|
|
6b32c4997d | ||
|
|
cb89c96dac | ||
|
|
b31ca263fd | ||
|
|
a0451cfab0 | ||
|
|
4baecd9f79 | ||
|
|
381851aba7 | ||
|
|
23d3d5973b | ||
|
|
fcdf9cf2ec | ||
|
|
9831236d79 | ||
|
|
c644d6bf2f | ||
|
|
d82b008c33 | ||
|
|
d53f9e99af | ||
|
|
870b168cea | ||
|
|
094e2478f9 | ||
|
|
110b9f5058 | ||
|
|
d959753641 | ||
|
|
027ecf29ae | ||
|
|
d4392635bf | ||
|
|
c72370eff0 | ||
|
|
6b799be7dd | ||
|
|
244d5bba47 | ||
|
|
d539ddf018 | ||
|
|
f279792273 | ||
|
|
aa4a793566 | ||
|
|
1aaa2340f8 | ||
|
|
329247391b | ||
|
|
3fc5daef01 | ||
|
|
e087d04c17 | ||
|
|
a3a713b608 | ||
|
|
98251a8631 | ||
|
|
b147a16487 | ||
|
|
411b86197a | ||
|
|
8acab6b662 | ||
|
|
dba550550c | ||
|
|
74cfc733dd | ||
|
|
23981accd1 | ||
|
|
209f34124e | ||
|
|
14cff4facf | ||
|
|
dac089b0fb | ||
|
|
52aeda7faf | ||
|
|
5273f5c9b6 | ||
|
|
d2a30ea049 | ||
|
|
716ba3eea8 | ||
|
|
5cece53e50 | ||
|
|
ba73f8a323 | ||
|
|
81379a49a7 | ||
|
|
77e4862b3e | ||
|
|
1a3bd2aa74 | ||
|
|
9997b4a2de | ||
|
|
74b12606ef | ||
|
|
14d5034b8e | ||
|
|
8b282818e9 | ||
|
|
48a4f8ba67 | ||
|
|
1dd048eed4 | ||
|
|
e398d6af75 | ||
|
|
39b1c1f7e4 | ||
|
|
ff78b384ac | ||
|
|
d45a6f94f5 | ||
|
|
48fe8d5762 | ||
|
|
36b493f03a | ||
|
|
670ccaf891 | ||
|
|
d16a48a1a2 | ||
|
|
3f4eedbeab | ||
|
|
ef3dd4a833 | ||
|
|
fee074db42 | ||
|
|
5463af7f60 | ||
|
|
b776cd4a91 | ||
|
|
45945e839f | ||
|
|
043ad7e3aa | ||
|
|
d04e186dea | ||
|
|
4253365e03 | ||
|
|
07437b7880 | ||
|
|
5ddd007f67 | ||
|
|
814978a178 | ||
|
|
5350e77125 | ||
|
|
22e2e32377 | ||
|
|
58b2c36498 | ||
|
|
0e4af7dbcf | ||
|
|
3aba1c5ccf | ||
|
|
44af80f336 | ||
|
|
4591f5b372 | ||
|
|
2270b2a224 | ||
|
|
4eb68aae77 | ||
|
|
c92e3cca6b | ||
|
|
13ba0121d7 | ||
|
|
be4943a9d6 | ||
|
|
9484e3b52d | ||
|
|
ef731703e8 | ||
|
|
11b86806de | ||
|
|
b872f59a01 | ||
|
|
b2fc1cc1f2 | ||
|
|
6907c9c550 | ||
|
|
93701585f8 | ||
|
|
50e596e63a | ||
|
|
51ece30c34 | ||
|
|
110a078074 | ||
|
|
85b222d9e2 | ||
|
|
c974d4f30a | ||
|
|
75916a21a6 | ||
|
|
8e2fc40105 | ||
|
|
f81bd857ed | ||
|
|
e7ac05db75 | ||
|
|
eb3f526a96 | ||
|
|
371b0bdce1 | ||
|
|
06e70e7476 | ||
|
|
a748fe6610 | ||
|
|
f43dd4d3dc | ||
|
|
9c01b18922 | ||
|
|
932fd032e8 | ||
|
|
c392deaf38 | ||
|
|
c575c1bea3 | ||
|
|
38ec5efa32 | ||
|
|
70764e73c3 | ||
|
|
03fd7fba79 | ||
|
|
33d56a287b | ||
|
|
c3523f93aa | ||
|
|
26f08c7a30 | ||
|
|
a3266cda45 | ||
|
|
80052fabe8 | ||
|
|
3819a845f0 | ||
|
|
d66c0f2d61 | ||
|
|
0bf9da71ab | ||
|
|
a07ff51c68 | ||
|
|
48faabee49 | ||
|
|
d2f9eeab12 | ||
|
|
c8ba4f0379 | ||
|
|
fba0fe1866 | ||
|
|
9685b92bc9 | ||
|
|
3ae9779eec | ||
|
|
ebbdc46305 | ||
|
|
4fb1c866da | ||
|
|
431d5ef8a8 | ||
|
|
174d1f7838 | ||
|
|
b57ecea305 | ||
|
|
64f39dcb79 | ||
|
|
86149145f6 | ||
|
|
f3c41e9a66 | ||
|
|
2a6723f8db | ||
|
|
2bb8d8b52a | ||
|
|
ac86cf19a0 | ||
|
|
ae29fef6d4 | ||
|
|
54486cb5be | ||
|
|
22e41df8ea | ||
|
|
d6dfc2ffb6 | ||
|
|
cb9d0ce3ed | ||
|
|
cef5117e29 | ||
|
|
466af94499 | ||
|
|
ab9dea6930 | ||
|
|
cc2c585a55 | ||
|
|
f9b1a59368 | ||
|
|
23cbe917c2 | ||
|
|
971f9694da | ||
|
|
32f057fa26 | ||
|
|
8c7edaaca4 | ||
|
|
205323200a | ||
|
|
9b880eb669 | ||
|
|
3ce53cedc8 | ||
|
|
37e44968dc | ||
|
|
4fb58bc005 | ||
|
|
b24832dd84 | ||
|
|
57aa32f0c1 | ||
|
|
60c20cc628 | ||
|
|
eaae999878 | ||
|
|
f0af71d8c0 | ||
|
|
b65fed7a0d | ||
|
|
364205cd9b | ||
|
|
081ea769a1 | ||
|
|
7fef69635f | ||
|
|
e3c3315873 | ||
|
|
bb0218d3c6 | ||
|
|
4e3e721c5a | ||
|
|
79321a0ffb | ||
|
|
230644141a | ||
|
|
480fa113b7 | ||
|
|
9016d937a3 | ||
|
|
a361576f8a | ||
|
|
bee695afd4 | ||
|
|
f3fcdf808f | ||
|
|
8e469493ac | ||
|
|
872df9ee70 | ||
|
|
69806f1260 | ||
|
|
0a24feab13 | ||
|
|
e40766583e | ||
|
|
fd3fb5041b | ||
|
|
8ffcaf328e | ||
|
|
5381e54469 | ||
|
|
f571685648 | ||
|
|
701d363e3c | ||
|
|
afa697031c | ||
|
|
7239c98a4c | ||
|
|
48c0297fe7 | ||
|
|
a02be14f1a | ||
|
|
a0b8c758b4 | ||
|
|
97a8029f74 | ||
|
|
4d6781b26b | ||
|
|
b2d24725de | ||
|
|
b04ac67860 | ||
|
|
61784fd48d | ||
|
|
f29738a96b | ||
|
|
cb313ed513 | ||
|
|
e23656a2cd | ||
|
|
8a39ff469f | ||
|
|
89c369189a | ||
|
|
1108c92beb | ||
|
|
e43cfce016 | ||
|
|
5931b53896 | ||
|
|
020b31cb6f | ||
|
|
8fc41c4c45 | ||
|
|
d9b3c2c15c | ||
|
|
10a9825f82 | ||
|
|
2bbe168200 | ||
|
|
d6e8a96b19 | ||
|
|
c34fae302e | ||
|
|
7957135979 | ||
|
|
c9330d0b7f | ||
|
|
9df5fc12cd | ||
|
|
dd64685049 | ||
|
|
eb54b90001 | ||
|
|
b1ba5790cc | ||
|
|
56d79f36dc | ||
|
|
bbcf7b638f | ||
|
|
94cc314738 | ||
|
|
39eb6482e3 | ||
|
|
85e66ebd8a | ||
|
|
322b7084ea | ||
|
|
8ac2c0462a | ||
|
|
24e2e29894 | ||
|
|
e00aeb39e5 | ||
|
|
6ec2849bd5 | ||
|
|
698c9d2923 | ||
|
|
82f379ecbe | ||
|
|
a502ceea53 | ||
|
|
53e4b39066 | ||
|
|
fceea79323 | ||
|
|
6adf49de45 | ||
|
|
0b85346ac4 | ||
|
|
53f0544da8 | ||
|
|
839ced1b32 | ||
|
|
b6fcbba57d | ||
|
|
b7f220d02b | ||
|
|
398ae8946c | ||
|
|
65357e1441 | ||
|
|
0a7f579bd8 | ||
|
|
2e0d2bb5e5 | ||
|
|
0612d447dc | ||
|
|
fd7e19d339 | ||
|
|
7f8dec161d | ||
|
|
26f50d109a | ||
|
|
acfdf23c22 | ||
|
|
a15588120b | ||
|
|
da28816967 | ||
|
|
53bf9922ca | ||
|
|
363ff9610d | ||
|
|
772c9bc77d | ||
|
|
c1f10c32c0 | ||
|
|
0a8c36e086 | ||
|
|
ae35e4b589 | ||
|
|
92a760541b | ||
|
|
3c67546f4a | ||
|
|
695670cc96 | ||
|
|
0f42aa4ec7 | ||
|
|
8aca69f845 | ||
|
|
63be38a3c3 | ||
|
|
e7f6e5c740 | ||
|
|
2ae8d49ec8 | ||
|
|
f9e987e933 | ||
|
|
8e9c76a9e8 | ||
|
|
ad52b60798 | ||
|
|
012cc28f8a | ||
|
|
1668717cf8 |
198
.gitattributes
vendored
198
.gitattributes
vendored
@@ -13,6 +13,7 @@ forge-ai/.settings/org.eclipse.core.resources.prefs -text
|
||||
forge-ai/.settings/org.eclipse.jdt.core.prefs -text
|
||||
forge-ai/.settings/org.eclipse.m2e.core.prefs -text
|
||||
forge-ai/pom.xml -text
|
||||
forge-ai/src/main/java/forge/ai/AIOption.java -text
|
||||
forge-ai/src/main/java/forge/ai/AiAttackController.java svneol=native#text/plain
|
||||
forge-ai/src/main/java/forge/ai/AiBlockController.java svneol=native#text/plain
|
||||
forge-ai/src/main/java/forge/ai/AiCardMemory.java -text
|
||||
@@ -172,14 +173,11 @@ forge-core/.settings/org.eclipse.core.resources.prefs -text
|
||||
forge-core/.settings/org.eclipse.jdt.core.prefs -text
|
||||
forge-core/.settings/org.eclipse.m2e.core.prefs -text
|
||||
forge-core/pom.xml -text
|
||||
forge-core/src/main/java/forge/AIOption.java -text
|
||||
forge-core/src/main/java/forge/CardStorageReader.java -text
|
||||
forge-core/src/main/java/forge/FTrace.java -text
|
||||
forge-core/src/main/java/forge/ImageKeys.java -text
|
||||
forge-core/src/main/java/forge/LobbyPlayer.java -text
|
||||
forge-core/src/main/java/forge/StaticData.java -text
|
||||
forge-core/src/main/java/forge/card/BoosterGenerator.java svneol=native#text/plain
|
||||
forge-core/src/main/java/forge/card/BoosterSlots.java -text
|
||||
forge-core/src/main/java/forge/card/CardAiHints.java -text
|
||||
forge-core/src/main/java/forge/card/CardChangedType.java -text
|
||||
forge-core/src/main/java/forge/card/CardDb.java -text
|
||||
@@ -199,10 +197,8 @@ forge-core/src/main/java/forge/card/ICardCharacteristics.java -text
|
||||
forge-core/src/main/java/forge/card/ICardDatabase.java -text
|
||||
forge-core/src/main/java/forge/card/ICardFace.java -text
|
||||
forge-core/src/main/java/forge/card/ICardRawAbilites.java -text
|
||||
forge-core/src/main/java/forge/card/IUnOpenedProduct.java -text
|
||||
forge-core/src/main/java/forge/card/MagicColor.java -text
|
||||
forge-core/src/main/java/forge/card/PrintSheet.java -text
|
||||
forge-core/src/main/java/forge/card/UnOpenedProduct.java -text
|
||||
forge-core/src/main/java/forge/card/mana/IParserManaCost.java -text
|
||||
forge-core/src/main/java/forge/card/mana/ManaAtom.java -text
|
||||
forge-core/src/main/java/forge/card/mana/ManaCost.java -text
|
||||
@@ -243,6 +239,10 @@ forge-core/src/main/java/forge/item/PaperToken.java -text
|
||||
forge-core/src/main/java/forge/item/PreconDeck.java -text
|
||||
forge-core/src/main/java/forge/item/SealedProduct.java -text
|
||||
forge-core/src/main/java/forge/item/TournamentPack.java -text
|
||||
forge-core/src/main/java/forge/item/generation/BoosterGenerator.java -text
|
||||
forge-core/src/main/java/forge/item/generation/BoosterSlots.java -text
|
||||
forge-core/src/main/java/forge/item/generation/IUnOpenedProduct.java -text
|
||||
forge-core/src/main/java/forge/item/generation/UnOpenedProduct.java -text
|
||||
forge-core/src/main/java/forge/item/package-info.java -text
|
||||
forge-core/src/main/java/forge/util/Aggregates.java -text
|
||||
forge-core/src/main/java/forge/util/Base64Coder.java -text
|
||||
@@ -304,6 +304,7 @@ forge-game/src/main/java/forge/GameCommand.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/CardTraitBase.java -text
|
||||
forge-game/src/main/java/forge/game/CardTraitPredicates.java -text svneol=unset#text/plain
|
||||
forge-game/src/main/java/forge/game/Direction.java -text
|
||||
forge-game/src/main/java/forge/game/ForgeScript.java -text
|
||||
forge-game/src/main/java/forge/game/Game.java -text
|
||||
forge-game/src/main/java/forge/game/GameAction.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/GameActionUtil.java svneol=native#text/plain
|
||||
@@ -494,6 +495,7 @@ forge-game/src/main/java/forge/game/card/CardLists.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/card/CardPlayOption.java -text
|
||||
forge-game/src/main/java/forge/game/card/CardPowerToughness.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/card/CardPredicates.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/card/CardProperty.java -text
|
||||
forge-game/src/main/java/forge/game/card/CardShields.java -text
|
||||
forge-game/src/main/java/forge/game/card/CardState.java -text
|
||||
forge-game/src/main/java/forge/game/card/CardUtil.java svneol=native#text/plain
|
||||
@@ -633,6 +635,7 @@ forge-game/src/main/java/forge/game/player/PlayerCollection.java -text svneol=un
|
||||
forge-game/src/main/java/forge/game/player/PlayerController.java -text
|
||||
forge-game/src/main/java/forge/game/player/PlayerOutcome.java -text
|
||||
forge-game/src/main/java/forge/game/player/PlayerPredicates.java -text svneol=unset#text/plain
|
||||
forge-game/src/main/java/forge/game/player/PlayerProperty.java -text
|
||||
forge-game/src/main/java/forge/game/player/PlayerStatistics.java -text
|
||||
forge-game/src/main/java/forge/game/player/PlayerView.java -text
|
||||
forge-game/src/main/java/forge/game/player/RegisteredPlayer.java -text
|
||||
@@ -688,6 +691,7 @@ forge-game/src/main/java/forge/game/staticability/StaticAbilityLayer.java -text
|
||||
forge-game/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/staticability/package-info.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/trigger/Trigger.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/trigger/TriggerAbandoned.java -text
|
||||
forge-game/src/main/java/forge/game/trigger/TriggerAlways.java svneol=native#text/plain
|
||||
forge-game/src/main/java/forge/game/trigger/TriggerAttached.java -text
|
||||
forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java svneol=native#text/plain
|
||||
@@ -1495,6 +1499,7 @@ forge-gui/res/blockdata/formats.txt -text
|
||||
forge-gui/res/blockdata/printsheets.txt -text
|
||||
forge-gui/res/blockdata/starters.txt -text
|
||||
forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt -text
|
||||
forge-gui/res/cardsfolder/a/a_reckoning_approaches.txt -text
|
||||
forge-gui/res/cardsfolder/a/abandon_hope.txt -text
|
||||
forge-gui/res/cardsfolder/a/abandon_reason.txt -text
|
||||
forge-gui/res/cardsfolder/a/abandoned_outpost.txt svneol=native#text/plain
|
||||
@@ -2064,6 +2069,7 @@ forge-gui/res/cardsfolder/a/arachnoid.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/a/arachnus_spinner.txt -text
|
||||
forge-gui/res/cardsfolder/a/arachnus_web.txt -text
|
||||
forge-gui/res/cardsfolder/a/aradara_express.txt -text
|
||||
forge-gui/res/cardsfolder/a/arahbo_roar_of_the_world.txt -text
|
||||
forge-gui/res/cardsfolder/a/arashi_the_sky_asunder.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/a/arashin_cleric.txt -text
|
||||
forge-gui/res/cardsfolder/a/arashin_foremost.txt -text
|
||||
@@ -2479,6 +2485,7 @@ forge-gui/res/cardsfolder/b/bakis_curse.txt -text
|
||||
forge-gui/res/cardsfolder/b/baku_altar.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bala_ged_scorpion.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bala_ged_thief.txt -text
|
||||
forge-gui/res/cardsfolder/b/balan_wandering_knight.txt -text
|
||||
forge-gui/res/cardsfolder/b/balance.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/balance_of_power.txt -text
|
||||
forge-gui/res/cardsfolder/b/balancing_act.txt -text
|
||||
@@ -2968,6 +2975,7 @@ forge-gui/res/cardsfolder/b/bloodfire_infusion.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodfire_kavu.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodfire_mentor.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodflow_connoisseur.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodforged_battle_axe.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodfray_giant.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodghast.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodgift_demon.txt -text
|
||||
@@ -2979,6 +2987,7 @@ forge-gui/res/cardsfolder/b/bloodhusk_ritualist.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodied_ghost.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodletter_quill.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodline_keeper_lord_of_lineage.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodline_necromancer.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodline_shaman.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodlord_of_vaasgoth.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodlust_inciter.txt -text
|
||||
@@ -2999,6 +3008,7 @@ forge-gui/res/cardsfolder/b/bloodspore_thrinax.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodstained_mire.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodstoke_howler.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodstone_cameo.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodsworn_steward.txt -text
|
||||
forge-gui/res/cardsfolder/b/bloodthirsty_ogre.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodthorn_taunter.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bloodthrone_vampire.txt svneol=native#text/plain
|
||||
@@ -3100,6 +3110,7 @@ forge-gui/res/cardsfolder/b/boneshard_slasher.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bonesplitter.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bonesplitter_sliver.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bonethorn_valesk.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/boneyard_scourge.txt -text
|
||||
forge-gui/res/cardsfolder/b/boneyard_wurm.txt -text
|
||||
forge-gui/res/cardsfolder/b/bonfire_of_the_damned.txt -text
|
||||
forge-gui/res/cardsfolder/b/bontu_the_glorified.txt -text
|
||||
@@ -3173,6 +3184,7 @@ forge-gui/res/cardsfolder/b/bounty_hunter.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/b/bounty_of_the_hunt.txt -text
|
||||
forge-gui/res/cardsfolder/b/bounty_of_the_luxa.txt -text
|
||||
forge-gui/res/cardsfolder/b/bow_of_nylea.txt -text
|
||||
forge-gui/res/cardsfolder/b/bow_to_my_command.txt -text
|
||||
forge-gui/res/cardsfolder/b/bower_passage.txt -text
|
||||
forge-gui/res/cardsfolder/b/brace_for_impact.txt -text
|
||||
forge-gui/res/cardsfolder/b/brackwater_elemental.txt svneol=native#text/plain
|
||||
@@ -3688,7 +3700,9 @@ forge-gui/res/cardsfolder/c/ceta_disciple.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/ceta_sanctuary.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/cetavolver.txt -text
|
||||
forge-gui/res/cardsfolder/c/chain_lightning.txt -text
|
||||
forge-gui/res/cardsfolder/c/chain_of_acid.txt -text
|
||||
forge-gui/res/cardsfolder/c/chain_of_plasma.txt -text
|
||||
forge-gui/res/cardsfolder/c/chain_of_silence.txt -text
|
||||
forge-gui/res/cardsfolder/c/chain_of_smog.txt -text
|
||||
forge-gui/res/cardsfolder/c/chain_of_vapor.txt -text
|
||||
forge-gui/res/cardsfolder/c/chain_reaction.txt svneol=native#text/plain
|
||||
@@ -3837,6 +3851,7 @@ forge-gui/res/cardsfolder/c/choking_sands.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/choking_tethers.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/choking_vines.txt -text
|
||||
forge-gui/res/cardsfolder/c/choose_your_champion.txt -text
|
||||
forge-gui/res/cardsfolder/c/choose_your_demise.txt -text
|
||||
forge-gui/res/cardsfolder/c/chord_of_calling.txt -text
|
||||
forge-gui/res/cardsfolder/c/chorus_of_might.txt -text
|
||||
forge-gui/res/cardsfolder/c/chorus_of_the_conclave.txt -text
|
||||
@@ -4364,6 +4379,7 @@ forge-gui/res/cardsfolder/c/crib_swap.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/crime_punishment.txt -text
|
||||
forge-gui/res/cardsfolder/c/crimson_acolyte.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/crimson_hellkite.txt -text
|
||||
forge-gui/res/cardsfolder/c/crimson_honor_guard.txt -text
|
||||
forge-gui/res/cardsfolder/c/crimson_kobolds.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/crimson_mage.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/crimson_manticore.txt svneol=native#text/plain
|
||||
@@ -4517,15 +4533,18 @@ forge-gui/res/cardsfolder/c/curiosity.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/curious_homunculus_voracious_reader.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_artifact.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_bloodletting.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_bounty.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_chains.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/curse_of_chaos.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_deaths_hold.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_disturbance.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_echoes.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_exhaustion.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_inertia.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_marit_lage.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/curse_of_misfortunes.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_oblivion.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_opulence.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_predation.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_shallow_graves.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_stalked_prey.txt -text
|
||||
@@ -4536,6 +4555,8 @@ forge-gui/res/cardsfolder/c/curse_of_the_nightly_hunt.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_the_pierced_heart.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_the_swine.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_thirst.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_verbosity.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_vitality.txt -text
|
||||
forge-gui/res/cardsfolder/c/curse_of_wizardry.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/c/cursebreak.txt -text
|
||||
forge-gui/res/cardsfolder/c/cursecatcher.txt svneol=native#text/plain
|
||||
@@ -4928,6 +4949,7 @@ forge-gui/res/cardsfolder/d/delay.txt -text
|
||||
forge-gui/res/cardsfolder/d/delaying_shield.txt -text
|
||||
forge-gui/res/cardsfolder/d/delifs_cone.txt -text
|
||||
forge-gui/res/cardsfolder/d/delifs_cube.txt -text
|
||||
forge-gui/res/cardsfolder/d/delight_in_the_hunt.txt -text
|
||||
forge-gui/res/cardsfolder/d/delirium.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/d/delirium_skeins.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/d/delraich.txt svneol=native#text/plain
|
||||
@@ -5186,6 +5208,7 @@ forge-gui/res/cardsfolder/d/display_of_dominance.txt -text
|
||||
forge-gui/res/cardsfolder/d/disposal_mummy.txt -text
|
||||
forge-gui/res/cardsfolder/d/dispossess.txt -text
|
||||
forge-gui/res/cardsfolder/d/disrupt.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/d/disrupt_decorum.txt -text
|
||||
forge-gui/res/cardsfolder/d/disrupting_scepter.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/d/disrupting_shoal.txt -text
|
||||
forge-gui/res/cardsfolder/d/disruption_aura.txt -text
|
||||
@@ -5642,6 +5665,7 @@ forge-gui/res/cardsfolder/e/echoing_decay.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/e/echoing_ruin.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/e/echoing_truth.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/e/eddytrail_hawk.txt -text
|
||||
forge-gui/res/cardsfolder/e/edgar_markov.txt -text
|
||||
forge-gui/res/cardsfolder/e/edge_of_autumn.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/e/edge_of_malacol.txt -text
|
||||
forge-gui/res/cardsfolder/e/edge_of_the_divinity.txt svneol=native#text/plain
|
||||
@@ -5970,6 +5994,7 @@ forge-gui/res/cardsfolder/e/erosion.txt -text
|
||||
forge-gui/res/cardsfolder/e/errand_of_duty.txt -text
|
||||
forge-gui/res/cardsfolder/e/errant_doomsayers.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/e/errant_ephemeron.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/e/errant_minion.txt -text
|
||||
forge-gui/res/cardsfolder/e/errantry.txt -text
|
||||
forge-gui/res/cardsfolder/e/erratic_explosion.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/e/erratic_mutation.txt -text
|
||||
@@ -6058,6 +6083,7 @@ forge-gui/res/cardsfolder/e/everlasting_torment.txt -text
|
||||
forge-gui/res/cardsfolder/e/evermind.txt -text
|
||||
forge-gui/res/cardsfolder/e/evernight_shade.txt -text
|
||||
forge-gui/res/cardsfolder/e/evershrike.txt -text
|
||||
forge-gui/res/cardsfolder/e/every_dream_a_nightmare.txt -text
|
||||
forge-gui/res/cardsfolder/e/every_hope_shall_vanish.txt -text
|
||||
forge-gui/res/cardsfolder/e/every_last_vestige_shall_rot.txt -text
|
||||
forge-gui/res/cardsfolder/e/evil_comes_to_fruition.txt -text
|
||||
@@ -6690,6 +6716,7 @@ forge-gui/res/cardsfolder/f/foot_soldiers.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/footbottom_feast.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/foothill_guide.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/footsteps_of_the_goryo.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/for_each_of_you_a_gift.txt -text
|
||||
forge-gui/res/cardsfolder/f/foratog.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/forbid.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/forbidden_alchemy.txt -text
|
||||
@@ -6756,6 +6783,7 @@ forge-gui/res/cardsfolder/f/fortitude.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/fortress_crab.txt -text
|
||||
forge-gui/res/cardsfolder/f/fortress_cyclops.txt -text
|
||||
forge-gui/res/cardsfolder/f/fortuitous_find.txt -text
|
||||
forge-gui/res/cardsfolder/f/fortunate_few.txt -text
|
||||
forge-gui/res/cardsfolder/f/fortune_thief.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/fortunes_favor.txt -text
|
||||
forge-gui/res/cardsfolder/f/fossil_find.txt -text
|
||||
@@ -6782,6 +6810,7 @@ forge-gui/res/cardsfolder/f/fountain_watch.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/fourth_bridge_prowler.txt -text
|
||||
forge-gui/res/cardsfolder/f/foxfire.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/foxfire_oak.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/fractured_identity.txt -text
|
||||
forge-gui/res/cardsfolder/f/fractured_loyalty.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/f/fractured_powerstone.txt -text
|
||||
forge-gui/res/cardsfolder/f/fracturing_gust.txt svneol=native#text/plain
|
||||
@@ -6921,6 +6950,7 @@ forge-gui/res/cardsfolder/g/gaeas_touch.txt -text
|
||||
forge-gui/res/cardsfolder/g/gahiji_honored_one.txt -text
|
||||
forge-gui/res/cardsfolder/g/gainsay.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/g/gale_force.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/g/galecaster_colossus.txt -text
|
||||
forge-gui/res/cardsfolder/g/galepowder_mage.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/g/galerider_sliver.txt -text
|
||||
forge-gui/res/cardsfolder/g/galestrike.txt -text
|
||||
@@ -7855,6 +7885,7 @@ forge-gui/res/cardsfolder/h/hamlet_captain.txt -text
|
||||
forge-gui/res/cardsfolder/h/hamletback_goliath.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/hammer_mage.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/hammer_of_bogardan.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/hammer_of_nazahn.txt -text
|
||||
forge-gui/res/cardsfolder/h/hammer_of_purphoros.txt -text
|
||||
forge-gui/res/cardsfolder/h/hammer_of_ruin.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/hammerfist_giant.txt svneol=native#text/plain
|
||||
@@ -7963,6 +7994,7 @@ forge-gui/res/cardsfolder/h/havoc_festival.txt -text
|
||||
forge-gui/res/cardsfolder/h/havoc_sower.txt -text
|
||||
forge-gui/res/cardsfolder/h/hawkeater_moth.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/hazardous_conditions.txt -text
|
||||
forge-gui/res/cardsfolder/h/hazduhr_the_abbot.txt -text
|
||||
forge-gui/res/cardsfolder/h/haze_frog.txt -text
|
||||
forge-gui/res/cardsfolder/h/haze_of_pollen.txt -text
|
||||
forge-gui/res/cardsfolder/h/haze_of_rage.txt svneol=native#text/plain
|
||||
@@ -8048,6 +8080,7 @@ forge-gui/res/cardsfolder/h/heidar_rimewind_master.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/heightened_awareness.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/heir_of_falkenrath_heir_to_the_night.txt -text
|
||||
forge-gui/res/cardsfolder/h/heir_of_the_wilds.txt -text
|
||||
forge-gui/res/cardsfolder/h/heirloom_blade.txt -text
|
||||
forge-gui/res/cardsfolder/h/heirs_of_stromkirk.txt -text
|
||||
forge-gui/res/cardsfolder/h/hekma_sentinels.txt -text
|
||||
forge-gui/res/cardsfolder/h/heliod_god_of_the_sun.txt -text
|
||||
@@ -8102,6 +8135,7 @@ forge-gui/res/cardsfolder/h/herald_of_the_host.txt -text
|
||||
forge-gui/res/cardsfolder/h/herald_of_the_pantheon.txt -text
|
||||
forge-gui/res/cardsfolder/h/herald_of_torment.txt -text
|
||||
forge-gui/res/cardsfolder/h/herald_of_war.txt -text
|
||||
forge-gui/res/cardsfolder/h/heralds_horn.txt -text
|
||||
forge-gui/res/cardsfolder/h/herbal_poultice.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/herd_gnarr.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/herdchaser_dragon.txt -text
|
||||
@@ -8369,6 +8403,7 @@ forge-gui/res/cardsfolder/h/hunger_of_the_howlpack.txt -text
|
||||
forge-gui/res/cardsfolder/h/hunger_of_the_nim.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/hungering_yeti.txt -text
|
||||
forge-gui/res/cardsfolder/h/hungry_flames.txt -text
|
||||
forge-gui/res/cardsfolder/h/hungry_lynx.txt -text
|
||||
forge-gui/res/cardsfolder/h/hungry_mist.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/hungry_spriggan.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/h/hunt_down.txt -text
|
||||
@@ -8563,6 +8598,7 @@ forge-gui/res/cardsfolder/i/in_oketras_name.txt -text
|
||||
forge-gui/res/cardsfolder/i/in_the_eye_of_chaos.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/i/in_the_web_of_war.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/i/inaction_injunction.txt -text
|
||||
forge-gui/res/cardsfolder/i/inalla_archmage_ritualist.txt -text
|
||||
forge-gui/res/cardsfolder/i/iname_as_one.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/i/iname_death_aspect.txt -text
|
||||
forge-gui/res/cardsfolder/i/iname_life_aspect.txt -text svneol=unset#text/plain
|
||||
@@ -8833,6 +8869,7 @@ forge-gui/res/cardsfolder/i/ixidors_will.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/i/ixidron.txt -text
|
||||
forge-gui/res/cardsfolder/i/izzet_boilerworks.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/i/izzet_charm.txt -text
|
||||
forge-gui/res/cardsfolder/i/izzet_chemister.txt -text
|
||||
forge-gui/res/cardsfolder/i/izzet_chronarch.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/i/izzet_cluestone.txt -text
|
||||
forge-gui/res/cardsfolder/i/izzet_guildgate.txt -text
|
||||
@@ -9175,6 +9212,7 @@ forge-gui/res/cardsfolder/k/kembas_skyguard.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/k/kemuri_onna.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/k/kentaro_the_smiling_cat.txt -text
|
||||
forge-gui/res/cardsfolder/k/keranos_god_of_storms.txt -text
|
||||
forge-gui/res/cardsfolder/k/kess_dissident_mage.txt -text
|
||||
forge-gui/res/cardsfolder/k/kessig.txt -text
|
||||
forge-gui/res/cardsfolder/k/kessig_cagebreakers.txt -text
|
||||
forge-gui/res/cardsfolder/k/kessig_dire_swine.txt -text
|
||||
@@ -9200,6 +9238,7 @@ forge-gui/res/cardsfolder/k/kher_keep.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/k/kheru_bloodsucker.txt -text
|
||||
forge-gui/res/cardsfolder/k/kheru_dreadmaw.txt -text
|
||||
forge-gui/res/cardsfolder/k/kheru_lich_lord.txt -text
|
||||
forge-gui/res/cardsfolder/k/kheru_mind_eater.txt -text
|
||||
forge-gui/res/cardsfolder/k/kheru_spellsnatcher.txt -text
|
||||
forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/k/kiku_nights_flower.txt svneol=native#text/plain
|
||||
@@ -9223,6 +9262,11 @@ forge-gui/res/cardsfolder/k/kindle.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/k/kindle_the_carnage.txt -text
|
||||
forge-gui/res/cardsfolder/k/kindled_fury.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/k/kindly_stranger_demon_possessed_witch.txt -text
|
||||
forge-gui/res/cardsfolder/k/kindred_boon.txt -text
|
||||
forge-gui/res/cardsfolder/k/kindred_charge.txt -text
|
||||
forge-gui/res/cardsfolder/k/kindred_discovery.txt -text
|
||||
forge-gui/res/cardsfolder/k/kindred_dominance.txt -text
|
||||
forge-gui/res/cardsfolder/k/kindred_summons.txt -text
|
||||
forge-gui/res/cardsfolder/k/king_cheetah.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/k/king_crab.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/k/king_macar_the_gold_cursed.txt -text
|
||||
@@ -9675,7 +9719,9 @@ forge-gui/res/cardsfolder/l/lich_lord_of_unx.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/l/lichenthrope.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/l/lichs_mirror.txt -text
|
||||
forge-gui/res/cardsfolder/l/lichs_tomb.txt -text
|
||||
forge-gui/res/cardsfolder/l/licia_sanguine_tribune.txt -text
|
||||
forge-gui/res/cardsfolder/l/liege_of_the_axe.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/l/liege_of_the_hollows.txt -text
|
||||
forge-gui/res/cardsfolder/l/liege_of_the_pit.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/l/liege_of_the_tangle.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/l/lieutenant_kirtar.txt svneol=native#text/plain
|
||||
@@ -10058,6 +10104,7 @@ forge-gui/res/cardsfolder/m/magus_of_the_disk.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_future.txt -text
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_jar.txt -text
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_library.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_mind.txt -text
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_mirror.txt -text
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_moat.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_moon.txt svneol=native#text/plain
|
||||
@@ -10068,12 +10115,14 @@ forge-gui/res/cardsfolder/m/magus_of_the_vineyard.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_wheel.txt -text
|
||||
forge-gui/res/cardsfolder/m/magus_of_the_will.txt -text
|
||||
forge-gui/res/cardsfolder/m/mahamoti_djinn.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/mairsil_the_pretender.txt -text
|
||||
forge-gui/res/cardsfolder/m/majestic_myriarch.txt -text
|
||||
forge-gui/res/cardsfolder/m/major_teroh.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/make_a_stand.txt -text
|
||||
forge-gui/res/cardsfolder/m/make_a_wish.txt -text
|
||||
forge-gui/res/cardsfolder/m/make_mischief.txt -text
|
||||
forge-gui/res/cardsfolder/m/make_obsolete.txt -text
|
||||
forge-gui/res/cardsfolder/m/make_yourself_useful.txt -text
|
||||
forge-gui/res/cardsfolder/m/makeshift_mannequin.txt -text svneol=unset#text/plain
|
||||
forge-gui/res/cardsfolder/m/makeshift_mauler.txt -text
|
||||
forge-gui/res/cardsfolder/m/makindi_aeronaut.txt -text
|
||||
@@ -10305,6 +10354,7 @@ forge-gui/res/cardsfolder/m/mastery_of_the_unseen.txt -text
|
||||
forge-gui/res/cardsfolder/m/masticore.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/masumaro_first_to_live.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/matca_rioters.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/mathas_fiend_seeker.txt -text
|
||||
forge-gui/res/cardsfolder/m/matopi_golem.txt -text
|
||||
forge-gui/res/cardsfolder/m/matsu_tribe_birdstalker.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/matsu_tribe_decoy.txt -text
|
||||
@@ -10623,6 +10673,7 @@ forge-gui/res/cardsfolder/m/mirri.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirri_cat_warrior.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/mirri_the_cursed.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/mirri_the_cursed_avatar.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirri_weatherlight_duelist.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirris_guile.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/mirrodins_core.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/mirror_entity.txt svneol=native#text/plain
|
||||
@@ -10633,6 +10684,7 @@ forge-gui/res/cardsfolder/m/mirror_mad_phantasm.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirror_match.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirror_mockery.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirror_of_fate.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirror_of_the_forebears.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirror_sheen.txt -text
|
||||
forge-gui/res/cardsfolder/m/mirror_sigil_sergeant.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/mirror_strike.txt -text
|
||||
@@ -10964,7 +11016,9 @@ forge-gui/res/cardsfolder/m/mwonvuli_acid_moss.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/mwonvuli_beast_tracker.txt -text
|
||||
forge-gui/res/cardsfolder/m/mwonvuli_ooze.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/m/my_crushing_masterstroke.txt -text svneol=unset#text/plain
|
||||
forge-gui/res/cardsfolder/m/my_forces_are_innumerable.txt -text
|
||||
forge-gui/res/cardsfolder/m/my_genius_knows_no_bounds.txt -text
|
||||
forge-gui/res/cardsfolder/m/my_laughter_echoes.txt -text
|
||||
forge-gui/res/cardsfolder/m/my_undead_horde_awakens.txt -text
|
||||
forge-gui/res/cardsfolder/m/my_wish_is_your_command.txt -text
|
||||
forge-gui/res/cardsfolder/m/mycoid_shepherd.txt svneol=native#text/plain
|
||||
@@ -11114,6 +11168,7 @@ forge-gui/res/cardsfolder/n/naya_hushblade.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/n/naya_panorama.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/n/naya_sojourners.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/n/naya_soulbeast.txt -text
|
||||
forge-gui/res/cardsfolder/n/nazahn_revered_bladesmith.txt -text
|
||||
forge-gui/res/cardsfolder/n/near_death_experience.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/n/nearheath_chaplain.txt -text
|
||||
forge-gui/res/cardsfolder/n/nearheath_pilgrim.txt -text
|
||||
@@ -11229,6 +11284,7 @@ forge-gui/res/cardsfolder/n/nevermaker.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/n/nevermore.txt -text
|
||||
forge-gui/res/cardsfolder/n/nevinyrrals_disk.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/n/new_benalia.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/n/new_blood.txt -text
|
||||
forge-gui/res/cardsfolder/n/new_frontiers.txt -text
|
||||
forge-gui/res/cardsfolder/n/new_perspectives.txt -text
|
||||
forge-gui/res/cardsfolder/n/new_prahv_guildmage.txt -text
|
||||
@@ -11441,6 +11497,7 @@ forge-gui/res/cardsfolder/n/nyxborn_rollicker.txt -text
|
||||
forge-gui/res/cardsfolder/n/nyxborn_shieldmate.txt -text
|
||||
forge-gui/res/cardsfolder/n/nyxborn_triton.txt -text
|
||||
forge-gui/res/cardsfolder/n/nyxborn_wolf.txt -text
|
||||
forge-gui/res/cardsfolder/o/o-kagachi_vengeful_kami.txt -text
|
||||
forge-gui/res/cardsfolder/o/o_naginata.txt -text
|
||||
forge-gui/res/cardsfolder/o/oak_street_innkeeper.txt -text
|
||||
forge-gui/res/cardsfolder/o/oaken_brawler.txt svneol=native#text/plain
|
||||
@@ -11888,6 +11945,7 @@ forge-gui/res/cardsfolder/p/past_in_flames.txt -text
|
||||
forge-gui/res/cardsfolder/p/patagia_golem.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/patagia_viper.txt -text
|
||||
forge-gui/res/cardsfolder/p/patchwork_gnomes.txt -text
|
||||
forge-gui/res/cardsfolder/p/path_of_ancestry.txt -text
|
||||
forge-gui/res/cardsfolder/p/path_of_angers_flame.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/path_of_bravery.txt -text
|
||||
forge-gui/res/cardsfolder/p/path_of_peace.txt svneol=native#text/plain
|
||||
@@ -11908,6 +11966,7 @@ forge-gui/res/cardsfolder/p/patron_of_the_moon.txt -text
|
||||
forge-gui/res/cardsfolder/p/patron_of_the_nezumi.txt -text
|
||||
forge-gui/res/cardsfolder/p/patron_of_the_orochi.txt -text
|
||||
forge-gui/res/cardsfolder/p/patron_of_the_valiant.txt -text
|
||||
forge-gui/res/cardsfolder/p/patron_of_the_vein.txt -text
|
||||
forge-gui/res/cardsfolder/p/patron_of_the_wild.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/patron_wizard.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/pattern_of_rebirth.txt svneol=native#text/plain
|
||||
@@ -12285,6 +12344,7 @@ forge-gui/res/cardsfolder/p/powder_keg.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/power_armor.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/power_artifact.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/power_conduit.txt -text
|
||||
forge-gui/res/cardsfolder/p/power_leak.txt -text
|
||||
forge-gui/res/cardsfolder/p/power_matrix.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/power_of_fire.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/p/power_play.txt -text
|
||||
@@ -12597,6 +12657,7 @@ forge-gui/res/cardsfolder/q/qarsi_high_priest.txt -text
|
||||
forge-gui/res/cardsfolder/q/qarsi_sadist.txt -text
|
||||
forge-gui/res/cardsfolder/q/qasali_ambusher.txt -text
|
||||
forge-gui/res/cardsfolder/q/qasali_pridemage.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/q/qasali_slingers.txt -text
|
||||
forge-gui/res/cardsfolder/q/quag_sickness.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/q/quag_vampires.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/q/quagmire.txt svneol=native#text/plain
|
||||
@@ -12767,6 +12828,7 @@ forge-gui/res/cardsfolder/r/rally_the_peasants.txt -text
|
||||
forge-gui/res/cardsfolder/r/rally_the_righteous.txt -text
|
||||
forge-gui/res/cardsfolder/r/rally_the_troops.txt -text
|
||||
forge-gui/res/cardsfolder/r/ramirez_depietro.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/r/ramos_dragon_engine.txt -text
|
||||
forge-gui/res/cardsfolder/r/ramosian_captain.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/r/ramosian_commander.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/r/ramosian_lieutenant.txt svneol=native#text/plain
|
||||
@@ -13799,6 +13861,7 @@ forge-gui/res/cardsfolder/s/scaled_behemoth.txt -text
|
||||
forge-gui/res/cardsfolder/s/scaled_hulk.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/s/scaled_wurm.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/s/scaleguard_sentinels.txt -text
|
||||
forge-gui/res/cardsfolder/s/scalelord_reckoner.txt -text
|
||||
forge-gui/res/cardsfolder/s/scalpelexis.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/s/scandalmonger.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/s/scapegoat.txt svneol=native#text/plain
|
||||
@@ -14329,6 +14392,7 @@ forge-gui/res/cardsfolder/s/shieldmates_blessing.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/s/shields_of_velis_vel.txt -text
|
||||
forge-gui/res/cardsfolder/s/shifting_borders.txt -text
|
||||
forge-gui/res/cardsfolder/s/shifting_loyalties.txt -text
|
||||
forge-gui/res/cardsfolder/s/shifting_shadow.txt -text
|
||||
forge-gui/res/cardsfolder/s/shifting_sky.txt -text
|
||||
forge-gui/res/cardsfolder/s/shifting_sliver.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/s/shifting_wall.txt svneol=native#text/plain
|
||||
@@ -15938,6 +16002,8 @@ forge-gui/res/cardsfolder/t/tahngarth_talruum_hero.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/t/tahngarths_glare.txt -text
|
||||
forge-gui/res/cardsfolder/t/tahngarths_rage.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/t/taiga.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/t/taigam_ojutai_master.txt -text
|
||||
forge-gui/res/cardsfolder/t/taigam_sidisis_hand.txt -text
|
||||
forge-gui/res/cardsfolder/t/taigams_scheming.txt -text
|
||||
forge-gui/res/cardsfolder/t/taigams_strike.txt -text
|
||||
forge-gui/res/cardsfolder/t/tail_slash.txt -text
|
||||
@@ -16079,6 +16145,7 @@ forge-gui/res/cardsfolder/t/teferis_honor_guard.txt -text
|
||||
forge-gui/res/cardsfolder/t/teferis_imp.txt -text
|
||||
forge-gui/res/cardsfolder/t/teferis_isle.txt -text
|
||||
forge-gui/res/cardsfolder/t/teferis_moat.txt -text
|
||||
forge-gui/res/cardsfolder/t/teferis_protection.txt -text
|
||||
forge-gui/res/cardsfolder/t/teferis_puzzle_box.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/t/teferis_realm.txt -text
|
||||
forge-gui/res/cardsfolder/t/teferis_response.txt svneol=native#text/plain
|
||||
@@ -16271,6 +16338,7 @@ forge-gui/res/cardsfolder/t/the_scarab_god.txt -text
|
||||
forge-gui/res/cardsfolder/t/the_scorpion_god.txt -text
|
||||
forge-gui/res/cardsfolder/t/the_tabernacle_at_pendrell_vale.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/t/the_unspeakable.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/t/the_ur_dragon.txt -text
|
||||
forge-gui/res/cardsfolder/t/the_very_soil_shall_shake.txt -text
|
||||
forge-gui/res/cardsfolder/t/the_wretched.txt -text svneol=unset#text/plain
|
||||
forge-gui/res/cardsfolder/t/the_zephyr_maze.txt -text
|
||||
@@ -16730,6 +16798,7 @@ forge-gui/res/cardsfolder/t/travelers_amulet.txt -text
|
||||
forge-gui/res/cardsfolder/t/travelers_cloak.txt -text
|
||||
forge-gui/res/cardsfolder/t/traveling_philosopher.txt -text
|
||||
forge-gui/res/cardsfolder/t/traveling_plague.txt -text
|
||||
forge-gui/res/cardsfolder/t/traverse_the_outlands.txt -text
|
||||
forge-gui/res/cardsfolder/t/traverse_the_ulvenwald.txt -text
|
||||
forge-gui/res/cardsfolder/t/treacherous_link.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/t/treacherous_pit_dweller.txt -text
|
||||
@@ -17134,10 +17203,6 @@ forge-gui/res/cardsfolder/u/utter_end.txt -text
|
||||
forge-gui/res/cardsfolder/u/utvara_hellkite.txt -text
|
||||
forge-gui/res/cardsfolder/u/utvara_scalper.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/u/uyo_silent_prophet.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/upcoming/ramos_dragon_engine.txt -text
|
||||
forge-gui/res/cardsfolder/upcoming/taigam_ojutai_master.txt -text
|
||||
forge-gui/res/cardsfolder/upcoming/the_ur_dragon.txt -text
|
||||
forge-gui/res/cardsfolder/upcoming/wasitora_nekoru_queen.txt -text
|
||||
forge-gui/res/cardsfolder/v/vacuumelt.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/v/vaevictis_asmadi.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/v/vagrant_plowbeasts.txt svneol=native#text/plain
|
||||
@@ -17398,6 +17463,7 @@ forge-gui/res/cardsfolder/v/villagers_of_estwald_howlpack_of_estwald.txt -text
|
||||
forge-gui/res/cardsfolder/v/villainous_ogre.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/v/villainous_wealth.txt -text
|
||||
forge-gui/res/cardsfolder/v/vindicate.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/v/vindictive_lich.txt -text
|
||||
forge-gui/res/cardsfolder/v/vindictive_mob.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/v/vine_dryad.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/v/vine_kami.txt -text
|
||||
@@ -17765,6 +17831,7 @@ forge-gui/res/cardsfolder/w/wars_toll.txt -text
|
||||
forge-gui/res/cardsfolder/w/warstorm_surge.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/warthog.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/wash_out.txt -text
|
||||
forge-gui/res/cardsfolder/w/wasitora_nekoru_queen.txt -text
|
||||
forge-gui/res/cardsfolder/w/wasp_lancer.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/wasp_of_the_bitter_end.txt -text
|
||||
forge-gui/res/cardsfolder/w/waste_away.txt svneol=native#text/plain
|
||||
@@ -18188,6 +18255,7 @@ forge-gui/res/cardsfolder/w/wormfang_drake.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/wormfang_manta.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/wormfang_newt.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/wormfang_turtle.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/worms_of_the_earth.txt -text
|
||||
forge-gui/res/cardsfolder/w/wormwood_dryad.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/wormwood_treefolk.txt svneol=native#text/plain
|
||||
forge-gui/res/cardsfolder/w/worn_powerstone.txt svneol=native#text/plain
|
||||
@@ -18509,6 +18577,7 @@ forge-gui/res/conquest/planes/Amonkhet/Shefet[!!-~]Dunes/Rise[!!-~]of[!!-~]the[!
|
||||
forge-gui/res/conquest/planes/Amonkhet/Shefet[!!-~]Dunes/Temmet,[!!-~]Vizier[!!-~]of[!!-~]Naktamun.dck -text
|
||||
forge-gui/res/conquest/planes/Amonkhet/Shefet[!!-~]Dunes/Trial[!!-~]of[!!-~]Solidarity.dck -text
|
||||
forge-gui/res/conquest/planes/Amonkhet/Shefet[!!-~]Dunes/_events.txt -text
|
||||
forge-gui/res/conquest/planes/Amonkhet/cards.txt -text
|
||||
forge-gui/res/conquest/planes/Amonkhet/plane_cards.txt -text
|
||||
forge-gui/res/conquest/planes/Amonkhet/regions.txt -text
|
||||
forge-gui/res/conquest/planes/Amonkhet/sets.txt -text
|
||||
@@ -19386,11 +19455,13 @@ forge-gui/res/licenses/xstream-license.txt svneol=native#text/plain
|
||||
forge-gui/res/lists/NonStackingKWList.txt svneol=native#text/plain
|
||||
forge-gui/res/lists/TypeLists.txt svneol=native#text/plain
|
||||
forge-gui/res/lists/achievement-images.txt -text
|
||||
forge-gui/res/lists/altwin-archivements.txt -text svneol=unset#text/plain
|
||||
forge-gui/res/lists/booster-images.txt svneol=native#text/plain
|
||||
forge-gui/res/lists/boosterbox-images.txt -text
|
||||
forge-gui/res/lists/fatpack-images.txt svneol=native#text/plain
|
||||
forge-gui/res/lists/net-decks-commander.txt -text
|
||||
forge-gui/res/lists/net-decks.txt -text
|
||||
forge-gui/res/lists/planeswalker-archivements.txt -text svneol=unset#text/plain
|
||||
forge-gui/res/lists/precon-images.txt svneol=native#text/plain
|
||||
forge-gui/res/lists/quest-opponent-icons.txt svneol=native#text/plain
|
||||
forge-gui/res/lists/quest-pet-token-images.txt svneol=native#text/plain
|
||||
@@ -19405,6 +19476,31 @@ forge-gui/res/music/menus/Evil[!!-~]March.mp3 -text
|
||||
forge-gui/res/music/menus/Heroic[!!-~]Age.mp3 -text
|
||||
forge-gui/res/music/menus/Lord[!!-~]of[!!-~]the[!!-~]Land.mp3 -text
|
||||
forge-gui/res/music/menus/The[!!-~]Pyre.mp3 -text
|
||||
forge-gui/res/puzzle/PC_033115.pzl -text
|
||||
forge-gui/res/puzzle/PC_040715.pzl -text
|
||||
forge-gui/res/puzzle/PC_041415.pzl -text
|
||||
forge-gui/res/puzzle/PC_042815.pzl -text
|
||||
forge-gui/res/puzzle/PC_050515.pzl -text
|
||||
forge-gui/res/puzzle/PC_051215.pzl -text
|
||||
forge-gui/res/puzzle/PC_051915.pzl -text
|
||||
forge-gui/res/puzzle/PC_052615.pzl -text
|
||||
forge-gui/res/puzzle/PC_060215.pzl -text
|
||||
forge-gui/res/puzzle/PC_060915.pzl -text
|
||||
forge-gui/res/puzzle/PC_062315.pzl -text
|
||||
forge-gui/res/puzzle/PC_063015.pzl -text
|
||||
forge-gui/res/puzzle/PC_070715.pzl -text
|
||||
forge-gui/res/puzzle/PC_071415.pzl -text
|
||||
forge-gui/res/puzzle/PC_072115.pzl -text
|
||||
forge-gui/res/puzzle/PC_072815.pzl -text
|
||||
forge-gui/res/puzzle/PC_080415.pzl -text
|
||||
forge-gui/res/puzzle/PC_081115.pzl -text
|
||||
forge-gui/res/puzzle/PC_081815.pzl -text
|
||||
forge-gui/res/puzzle/PC_082515.pzl -text
|
||||
forge-gui/res/puzzle/PC_090115.pzl -text
|
||||
forge-gui/res/puzzle/PC_090815.pzl -text
|
||||
forge-gui/res/puzzle/PC_091515.pzl -text
|
||||
forge-gui/res/puzzle/PC_092215.pzl -text
|
||||
forge-gui/res/puzzle/PC_092915.pzl -text
|
||||
forge-gui/res/puzzle/PC_13.pzl -text
|
||||
forge-gui/res/puzzle/PC_18.pzl -text
|
||||
forge-gui/res/puzzle/PC_19.pzl -text
|
||||
@@ -19415,18 +19511,28 @@ forge-gui/res/puzzle/PP00.pzl -text
|
||||
forge-gui/res/puzzle/PP01.pzl -text
|
||||
forge-gui/res/puzzle/PP02.pzl -text
|
||||
forge-gui/res/puzzle/PP03.pzl -text
|
||||
forge-gui/res/puzzle/PP04.pzl -text
|
||||
forge-gui/res/puzzle/PP05.pzl -text
|
||||
forge-gui/res/puzzle/PP06.pzl -text
|
||||
forge-gui/res/puzzle/PP07.pzl -text
|
||||
forge-gui/res/puzzle/PP08.pzl -text
|
||||
forge-gui/res/puzzle/PP09.pzl -text
|
||||
forge-gui/res/puzzle/PP10.pzl -text
|
||||
forge-gui/res/puzzle/PP11.pzl -text
|
||||
forge-gui/res/puzzle/PP12.pzl -text
|
||||
forge-gui/res/puzzle/PP13.pzl -text
|
||||
forge-gui/res/puzzle/PP14.pzl -text
|
||||
forge-gui/res/puzzle/PP15.pzl -text
|
||||
forge-gui/res/puzzle/PP16.pzl -text
|
||||
forge-gui/res/puzzle/PP17.pzl -text
|
||||
forge-gui/res/puzzle/PP18.pzl -text
|
||||
forge-gui/res/puzzle/PP19.pzl -text
|
||||
forge-gui/res/puzzle/PP20.pzl -text
|
||||
forge-gui/res/puzzle/PP22.pzl -text
|
||||
forge-gui/res/puzzle/PP23.pzl -text
|
||||
forge-gui/res/puzzle/PP24.pzl -text
|
||||
forge-gui/res/puzzle/PP25.pzl -text
|
||||
forge-gui/res/puzzle/PP27.pzl -text
|
||||
forge-gui/res/puzzle/PP28.pzl -text
|
||||
forge-gui/res/puzzle/PP29.pzl -text
|
||||
forge-gui/res/puzzle/PP30.pzl -text
|
||||
@@ -19437,12 +19543,14 @@ forge-gui/res/puzzle/PS_AER4.pzl -text
|
||||
forge-gui/res/puzzle/PS_AER5.pzl -text
|
||||
forge-gui/res/puzzle/PS_AER6.pzl -text
|
||||
forge-gui/res/puzzle/PS_AER7.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH0.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH1.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH2.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH3.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH4.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH5.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH6.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH7.pzl -text
|
||||
forge-gui/res/puzzle/PS_AKH8.pzl -text
|
||||
forge-gui/res/quest/bazaar/ape_pet_l1.txt -text
|
||||
forge-gui/res/quest/bazaar/ape_pet_l2.txt -text
|
||||
@@ -19752,6 +19860,7 @@ forge-gui/res/quest/duels/Hookah-Smoking[!!-~]Caterpillar[!!-~]2.dck -text
|
||||
forge-gui/res/quest/duels/Hugo[!!-~]Drax[!!-~]1.dck -text
|
||||
forge-gui/res/quest/duels/Hugo[!!-~]Drax[!!-~]2.dck -text
|
||||
forge-gui/res/quest/duels/Hulk[!!-~]2.dck -text
|
||||
forge-gui/res/quest/duels/Ice[!!-~]King[!!-~]1.dck -text
|
||||
forge-gui/res/quest/duels/Iceman[!!-~]3.dck -text
|
||||
forge-gui/res/quest/duels/Immortus[!!-~]4.dck -text
|
||||
forge-gui/res/quest/duels/Imperial[!!-~]Guard[!!-~]2.dck -text
|
||||
@@ -20818,6 +20927,77 @@ forge-gui/res/quest/world/1997-05[!!-~]Portal/duels/POR[!!-~]3[!!-~]Reanimator.d
|
||||
forge-gui/res/quest/world/1997-05[!!-~]Portal/duels/POR[!!-~]3[!!-~]Stragegy[!!-~]Red[!!-~]Rock.dck -text
|
||||
forge-gui/res/quest/world/1997-05[!!-~]Portal/duels/POR[!!-~]3[!!-~]Strategy[!!-~]RDW.dck -text
|
||||
forge-gui/res/quest/world/1997-05[!!-~]Portal/duels/POR[!!-~]3[!!-~]Strategy[!!-~]Zoo.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Bee[!!-~]Sting.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Chorus[!!-~]of[!!-~]Woe.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Dakmor[!!-~]Plague.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Earthquake.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Foul[!!-~]Spirit.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Goblin[!!-~]War[!!-~]Strike.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Hidden[!!-~]Horror.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Kiss[!!-~]of[!!-~]Death.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Swarm[!!-~]of[!!-~]Rats.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Sylvan[!!-~]Yeti.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Talas[!!-~]Scout.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Challenge[!!-~]Volcanic[!!-~]Hammer.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/challenges/PO2[!!-~]Final[!!-~]Challenge[!!-~]Tojiras[!!-~]Finest.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]1[!!-~]Defenders[!!-~]of[!!-~]Trokin.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]1[!!-~]Heavenly[!!-~]Fury.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]1[!!-~]Protectors[!!-~]of[!!-~]Alaborn.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]2[!!-~]Mystic[!!-~]Masters.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]2[!!-~]Pirates[!!-~]of[!!-~]Talas.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]3[!!-~]Dangers[!!-~]of[!!-~]Dakmor.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]3[!!-~]The[!!-~]Nightstalker[!!-~]Menace.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]3[!!-~]Tojira,[!!-~]Swamp[!!-~]Queen.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]4[!!-~]Earth[!!-~]and[!!-~]Fire.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]4[!!-~]Mountain[!!-~]Giants.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]4[!!-~]The[!!-~]Goblin[!!-~]Horde.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]5[!!-~]The[!!-~]Deep[!!-~]Woods.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]5[!!-~]The[!!-~]Elves[!!-~]of[!!-~]Norwood.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Lore[!!-~]5[!!-~]Wild[!!-~]Beasts.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Starter[!!-~]Set[!!-~]for[!!-~]2[!!-~]Players[!!-~]1[!!-~]BRG.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Starter[!!-~]Set[!!-~]for[!!-~]2[!!-~]Players[!!-~]2[!!-~]GWU.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Theme[!!-~]Deck[!!-~]1[!!-~]Martial[!!-~]Law.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Theme[!!-~]Deck[!!-~]2[!!-~]Spellweaver.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Theme[!!-~]Deck[!!-~]3[!!-~]The[!!-~]Nightstalkers.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Theme[!!-~]Deck[!!-~]4[!!-~]Goblin[!!-~]Fire.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Theme[!!-~]Deck[!!-~]5[!!-~]Natures[!!-~]Assault.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Theme[!!-~]Earth,[!!-~]Wind[!!-~]and[!!-~]Fire.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]1[!!-~]Theme[!!-~]Elementary.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Bighand.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Black[!!-~]Plague.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Breath[!!-~]of[!!-~]Death.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Breath[!!-~]of[!!-~]Fire.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Burn.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Catch[!!-~]Me[!!-~]If[!!-~]You[!!-~]Can.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Deep[!!-~]Breath.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Deep[!!-~]Ramp.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Double[!!-~]Tap.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Giant[!!-~]Ramp.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Goblinpile.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Life[!!-~]is[!!-~]Life.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Nightstalker[!!-~]Lore.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Rat[!!-~]Catcher.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Recoil.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]White[!!-~]Weenie.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Wurmageddon.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Wurmcalling.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]2[!!-~]Gimmick[!!-~]Wurmfire.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Color[!!-~]1[!!-~]White.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Color[!!-~]2[!!-~]Blue.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Color[!!-~]3[!!-~]Black.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Color[!!-~]4[!!-~]Red.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Color[!!-~]5[!!-~]Green.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]01[!!-~]Classic[!!-~]Control.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]02[!!-~]Attrition.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]03[!!-~]Air[!!-~]Superiority.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]04[!!-~]Counterburn.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]05[!!-~]Midrange.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]05[!!-~]Suicide[!!-~]Aggro.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]06[!!-~]Survivors.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]07[!!-~]Wildfire.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]08[!!-~]Swarm[!!-~]Aggro.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]09[!!-~]Weenie[!!-~]Pump.dck -text
|
||||
forge-gui/res/quest/world/1998-06[!!-~]Portal[!!-~]Second[!!-~]Age/duels/PO2[!!-~]3[!!-~]Strategy[!!-~]10[!!-~]Tempo.dck -text
|
||||
forge-gui/res/quest/world/Urza/challenges/Ancient.dck -text
|
||||
forge-gui/res/quest/world/Urza/challenges/Bargain.dck -text
|
||||
forge-gui/res/quest/world/Urza/challenges/Contamination.dck -text
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.1</version>
|
||||
<version>1.6.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package forge;
|
||||
package forge.ai;
|
||||
|
||||
public enum AIOption {
|
||||
USE_SIMULATION;
|
||||
@@ -558,7 +558,11 @@ public class AiAttackController {
|
||||
|
||||
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
||||
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders());
|
||||
final int attackMax = restrict.getMax();
|
||||
int attackMax = restrict.getMax();
|
||||
if (attackMax == -1) {
|
||||
// check with the local limitations vs. the chosen defender
|
||||
attackMax = ComputerUtilCombat.getMaxAttackersFor(defender);
|
||||
}
|
||||
|
||||
if (attackMax == 0) {
|
||||
// can't attack anymore
|
||||
|
||||
@@ -151,7 +151,7 @@ public class AiBlockController {
|
||||
for (final Card c : attackers) {
|
||||
sortedAttackers.add(c);
|
||||
}
|
||||
} else {
|
||||
} else if (defender instanceof Player && defender.equals(ai)){
|
||||
firstAttacker = combat.getAttackersOf(defender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1306,7 +1306,7 @@ public class AiController {
|
||||
+ MyRandom.getRandom().nextInt(3);
|
||||
return Math.max(remaining, min) / 2;
|
||||
} else if ("LowestLoseLife".equals(logic)) {
|
||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getOpponent().getLife())) + 1;
|
||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, ComputerUtil.getOpponentFor(player).getLife())) + 1;
|
||||
} else if ("HighestGetCounter".equals(logic)) {
|
||||
return MyRandom.getRandom().nextInt(3);
|
||||
} else if (source.hasSVar("EnergyToPay")) {
|
||||
@@ -1428,6 +1428,24 @@ public class AiController {
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// Special case for Bow to My Command which simulates a complex tap cost via ChooseCard
|
||||
// TODO: consider enhancing support for tapXType<Any/...> in UnlessCost to get rid of this hack
|
||||
if ("BowToMyCommand".equals(sa.getParam("AILogic"))) {
|
||||
if (!sa.getHostCard().getZone().is(ZoneType.Command)) {
|
||||
// Make sure that other opponents do not tap for an already abandoned scheme
|
||||
result.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
int totPower = 0;
|
||||
for (Card p : result) {
|
||||
totPower += p.getNetPower();
|
||||
}
|
||||
if (totPower >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ public class ComputerUtil {
|
||||
if (unless != null && !unless.endsWith(">")) {
|
||||
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
||||
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ComputerUtil.getOpponentFor(ai), true).size();
|
||||
|
||||
// If the Unless isn't enough, this should be less likely to be used
|
||||
if (amount > usableManaSources) {
|
||||
@@ -926,7 +926,7 @@ public class ComputerUtil {
|
||||
return true;
|
||||
} else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) {
|
||||
//Only play these main1 when the opponent has creatures (stealing and giving them haste)
|
||||
if (!card.getController().getOpponent().getCreaturesInPlay().isEmpty()) {
|
||||
if (!ComputerUtil.getOpponentFor(card.getController()).getCreaturesInPlay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
} else if (!card.getController().getCreaturesInPlay().isEmpty()) {
|
||||
@@ -973,7 +973,7 @@ public class ComputerUtil {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ai.getOpponent())) {
|
||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
|
||||
return true;
|
||||
}
|
||||
if (card.isCreature()) {
|
||||
@@ -993,7 +993,7 @@ public class ComputerUtil {
|
||||
} // BuffedBy
|
||||
|
||||
// get all cards the human controls with AntiBuffedBy
|
||||
final CardCollectionView antibuffed = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
for (Card buffedcard : antibuffed) {
|
||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||
@@ -1033,11 +1033,11 @@ public class ComputerUtil {
|
||||
return ret;
|
||||
} else {
|
||||
// Otherwise, if life is possibly in danger, then this is fine.
|
||||
Combat combat = new Combat(ai.getOpponent());
|
||||
CardCollectionView attackers = ai.getOpponent().getCreaturesInPlay();
|
||||
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
|
||||
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||
for (Card att : attackers) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, att.getController().getOpponent());
|
||||
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
|
||||
}
|
||||
}
|
||||
AiBlockController aiBlock = new AiBlockController(ai);
|
||||
@@ -1101,7 +1101,7 @@ public class ComputerUtil {
|
||||
return (sa.getHostCard().isCreature()
|
||||
&& sa.getPayCosts().hasTapCost()
|
||||
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !ph.getNextTurn().equals(sa.getActivatingPlayer()))
|
||||
&& !ph.getNextTurn().equals(sa.getActivatingPlayer()))
|
||||
&& !sa.getHostCard().hasSVar("EndOfTurnLeavePlay")
|
||||
&& !sa.hasParam("ActivationPhases"));
|
||||
}
|
||||
@@ -1145,7 +1145,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
// get all cards the human controls with AntiBuffedBy
|
||||
final CardCollectionView antibuffed = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
for (Card buffedcard : antibuffed) {
|
||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||
@@ -1331,7 +1331,7 @@ public class ComputerUtil {
|
||||
if (tgt == null) {
|
||||
continue;
|
||||
}
|
||||
final Player enemy = ai.getOpponent();
|
||||
final Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
if (!sa.canTarget(enemy)) {
|
||||
continue;
|
||||
}
|
||||
@@ -2047,7 +2047,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
else if (logic.equals("ChosenLandwalk")) {
|
||||
for (Card c : ai.getOpponent().getLandsInPlay()) {
|
||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
||||
for (String t : c.getType()) {
|
||||
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
||||
chosen = t;
|
||||
@@ -2065,7 +2065,7 @@ public class ComputerUtil {
|
||||
else if (kindOfType.equals("Land")) {
|
||||
if (logic != null) {
|
||||
if (logic.equals("ChosenLandwalk")) {
|
||||
for (Card c : ai.getOpponent().getLandsInPlay()) {
|
||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
||||
for (String t : c.getType().getLandTypes()) {
|
||||
if (!invalidTypes.contains(t)) {
|
||||
chosen = t;
|
||||
@@ -2098,7 +2098,7 @@ public class ComputerUtil {
|
||||
case "Torture":
|
||||
return "Torture";
|
||||
case "GraceOrCondemnation":
|
||||
return ai.getCreaturesInPlay().size() > ai.getOpponent().getCreaturesInPlay().size() ? "Grace"
|
||||
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
||||
: "Condemnation";
|
||||
case "CarnageOrHomage":
|
||||
CardCollection cardsInPlay = CardLists
|
||||
@@ -2683,4 +2683,23 @@ public class ComputerUtil {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static final Player getOpponentFor(final Player player) {
|
||||
Player opponent = null;
|
||||
int minLife = Integer.MAX_VALUE;
|
||||
|
||||
for (Player p : player.getOpponents()) {
|
||||
if (p.getLife() < minLife) {
|
||||
opponent = p;
|
||||
minLife = p.getLife();
|
||||
}
|
||||
}
|
||||
|
||||
if (opponent != null) {
|
||||
return opponent;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("No opponents left ingame for " + player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ public class ComputerUtilCard {
|
||||
*/
|
||||
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||
AiBlockController aiBlk = new AiBlockController(ai);
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
Combat combat = new Combat(opp);
|
||||
//Use actual attackers if available, else consider all possible attackers
|
||||
Combat currentCombat = ai.getGame().getCombat();
|
||||
@@ -845,9 +845,10 @@ public class ComputerUtilCard {
|
||||
List<String> chosen = new ArrayList<String>();
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
if (logic.equals("MostProminentInHumanDeck")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices));
|
||||
}
|
||||
@@ -873,7 +874,7 @@ public class ComputerUtilCard {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield), colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentHumanControls")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(ai.getOpponent().getCardsIn(ZoneType.Battlefield), colorChoices));
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(opp.getCardsIn(ZoneType.Battlefield), colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentPermanent")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(game.getCardsIn(ZoneType.Battlefield), colorChoices));
|
||||
@@ -897,7 +898,7 @@ public class ComputerUtilCard {
|
||||
String bestColor = Constant.GREEN;
|
||||
for (byte color : MagicColor.WUBRG) {
|
||||
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
|
||||
CardCollectionView opplist = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
CardCollectionView opplist = opp.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
|
||||
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
||||
@@ -934,7 +935,7 @@ public class ComputerUtilCard {
|
||||
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Game game = ai.getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
final PhaseType phaseType = ph.getPhase();
|
||||
@@ -1217,7 +1218,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
}
|
||||
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords);
|
||||
List<Card> oppCreatures = opp.getCreaturesInPlay();
|
||||
float chance = 0;
|
||||
|
||||
@@ -1308,6 +1308,10 @@ public class ComputerUtilCombat {
|
||||
bonus = bonus.replace("TriggerCount$NumBlockers", "Number$1");
|
||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||
bonus = bonus.replace("TriggeredPlayersDefenders$Amount", "Number$1");
|
||||
} else if (bonus.contains("TriggeredAttacker$CardPower")) { // e.g. Arahbo, Roar of the World
|
||||
bonus = bonus.replace("TriggeredAttacker$CardPower", "Number$" + attacker.getNetPower());
|
||||
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
|
||||
bonus = bonus.replace("TriggeredAttacker$CardToughness", "Number$" + attacker.getNetToughness());
|
||||
}
|
||||
power += CardFactoryUtil.xCount(source, bonus);
|
||||
|
||||
@@ -1741,6 +1745,11 @@ public class ComputerUtilCombat {
|
||||
// consider Damage Prevention/Replacement
|
||||
defenderDamage = predictDamageTo(attacker, defenderDamage, possibleAttackerPrevention, blocker, true);
|
||||
attackerDamage = predictDamageTo(blocker, attackerDamage, possibleDefenderPrevention, attacker, true);
|
||||
if (!attacker.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
|
||||
if (defenderDamage > 0 && isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final int defenderLife = ComputerUtilCombat.getDamageToKill(blocker)
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, blocker, withoutAbilities);
|
||||
@@ -2431,6 +2440,20 @@ public class ComputerUtilCombat {
|
||||
int afflictDmg = attacker.getKeywordMagnitude("Afflict");
|
||||
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
|
||||
}
|
||||
|
||||
public static int getMaxAttackersFor(final GameEntity defender) {
|
||||
if (defender instanceof Player) {
|
||||
for (final Card card : ((Player) defender).getCardsIn(ZoneType.Battlefield)) {
|
||||
if (card.hasKeyword("No more than one creature can attack you each combat.")) {
|
||||
return 1;
|
||||
} else if (card.hasKeyword("No more than two creatures can attack you each combat.")) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -566,7 +566,7 @@ public class ComputerUtilCost {
|
||||
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
|
||||
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
|
||||
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
|
||||
&& (!source.getName().equals("Chain of Vapor") || (payer.getOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
|
||||
&& (!source.getName().equals("Chain of Vapor") || (ComputerUtil.getOpponentFor(payer).getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
|
||||
}
|
||||
|
||||
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {
|
||||
|
||||
@@ -219,25 +219,32 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
|
||||
SpellAbility paymentChoice = ma;
|
||||
|
||||
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
|
||||
// to attempt to make the spell uncounterable when possible.
|
||||
if ((toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)
|
||||
&& ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls")
|
||||
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls")
|
||||
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getHostCard().getChosenType())) {
|
||||
for (SpellAbility ab : saList) {
|
||||
if (ab.isManaAbility() && ab.getManaPart().isAnyMana() && ab.hasParam("AddsNoCounter")) {
|
||||
return ab;
|
||||
if (toPay == ManaCostShard.COLORLESS && cost.getUnpaidShards().contains(ManaCostShard.GENERIC)) {
|
||||
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
|
||||
continue;
|
||||
} else if (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X) {
|
||||
for (SpellAbility ab : saList) {
|
||||
if (ab.isManaAbility() && ab.getManaPart().isAnyMana() && ab.hasParam("AddsNoCounter")) {
|
||||
paymentChoice = ab;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String typeRes = cost.getSourceRestriction();
|
||||
if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().getType().hasStringType(typeRes)) {
|
||||
if (StringUtils.isNotBlank(typeRes) && !paymentChoice.getHostCard().getType().hasStringType(typeRes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canPayShardWithSpellAbility(toPay, ai, ma, sa, checkCosts)) {
|
||||
return ma;
|
||||
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts)) {
|
||||
return paymentChoice;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -4,15 +4,21 @@ import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.StaticData;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
@@ -23,14 +29,21 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.event.GameEventAttackersDeclared;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.PlayerZone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public abstract class GameState {
|
||||
private static final Map<ZoneType, String> ZONES = new HashMap<ZoneType, String>();
|
||||
@@ -53,12 +66,30 @@ public abstract class GameState {
|
||||
|
||||
private final Map<Integer, Card> idToCard = new HashMap<>();
|
||||
private final Map<Card, Integer> cardToAttachId = new HashMap<>();
|
||||
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
||||
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
||||
private final Map<Card, String> cardToChosenType = new HashMap<>();
|
||||
private final Map<Card, List<String>> cardToRememberedId = new HashMap<>();
|
||||
private final Map<Card, String> cardToExiledWithId = new HashMap<>();
|
||||
private final Map<Card, Card> cardAttackMap = new HashMap<>();
|
||||
|
||||
private final Map<Card, String> cardToScript = new HashMap<>();
|
||||
|
||||
private final Map<String, String> abilityString = new HashMap<>();
|
||||
|
||||
private final Set<Card> cardsReferencedByID = new HashSet<>();
|
||||
|
||||
private String tChangePlayer = "NONE";
|
||||
private String tChangePhase = "NONE";
|
||||
|
||||
|
||||
private String precastHuman = null;
|
||||
private String precastAI = null;
|
||||
|
||||
// Targeting for precast spells in a game state (mostly used by Puzzle Mode game states)
|
||||
private final int TARGET_NONE = -1; // untargeted spell (e.g. Joraga Invocation)
|
||||
private final int TARGET_HUMAN = -2;
|
||||
private final int TARGET_AI = -3;
|
||||
|
||||
public GameState() {
|
||||
}
|
||||
|
||||
@@ -110,6 +141,40 @@ public abstract class GameState {
|
||||
tChangePhase = game.getPhaseHandler().getPhase().toString();
|
||||
aiCardTexts.clear();
|
||||
humanCardTexts.clear();
|
||||
|
||||
// Mark the cards that need their ID remembered for various reasons
|
||||
cardsReferencedByID.clear();
|
||||
for (ZoneType zone : ZONES.keySet()) {
|
||||
for (Card card : game.getCardsIn(zone)) {
|
||||
if (card.getExiledWith() != null) {
|
||||
// Remember the ID of the card that exiled this card
|
||||
cardsReferencedByID.add(card.getExiledWith());
|
||||
}
|
||||
if (zone == ZoneType.Battlefield) {
|
||||
if (!card.getEnchantedBy(false).isEmpty()
|
||||
|| !card.getEquippedBy(false).isEmpty()
|
||||
|| !card.getFortifiedBy(false).isEmpty()) {
|
||||
// Remember the ID of cards that have attachments
|
||||
cardsReferencedByID.add(card);
|
||||
}
|
||||
}
|
||||
for (Object o : card.getRemembered()) {
|
||||
// Remember the IDs of remembered cards
|
||||
// TODO: we can currently support remembered cards only. Expand to support other remembered objects.
|
||||
if (o instanceof Card) {
|
||||
cardsReferencedByID.add((Card)o);
|
||||
}
|
||||
}
|
||||
if (game.getCombat() != null && game.getCombat().isAttacking(card)) {
|
||||
// Remember the IDs of attacked planeswalkers
|
||||
GameEntity def = game.getCombat().getDefenderByAttacker(card);
|
||||
if (def instanceof Card) {
|
||||
cardsReferencedByID.add((Card)def);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ZoneType zone : ZONES.keySet()) {
|
||||
// Init texts to empty, so that restoring will clear the state
|
||||
// if the zone had no cards in it (e.g. empty hand).
|
||||
@@ -140,6 +205,11 @@ public abstract class GameState {
|
||||
if (c.isCommander()) {
|
||||
newText.append("|IsCommander");
|
||||
}
|
||||
|
||||
if (cardsReferencedByID.contains(c)) {
|
||||
newText.append("|Id:").append(c.getId());
|
||||
}
|
||||
|
||||
if (zoneType == ZoneType.Battlefield) {
|
||||
if (c.isTapped()) {
|
||||
newText.append("|Tapped");
|
||||
@@ -147,6 +217,16 @@ public abstract class GameState {
|
||||
if (c.isSick()) {
|
||||
newText.append("|SummonSick");
|
||||
}
|
||||
if (c.isRenowned()) {
|
||||
newText.append("|Renowned");
|
||||
}
|
||||
if (c.isMonstrous()) {
|
||||
newText.append("|Monstrous:");
|
||||
newText.append(c.getMonstrosityNum());
|
||||
}
|
||||
if (c.isPhasedOut()) {
|
||||
newText.append("|PhasedOut");
|
||||
}
|
||||
if (c.isFaceDown()) {
|
||||
newText.append("|FaceDown");
|
||||
if (c.isManifested()) {
|
||||
@@ -155,6 +235,10 @@ public abstract class GameState {
|
||||
}
|
||||
if (c.getCurrentStateName().equals(CardStateName.Transformed)) {
|
||||
newText.append("|Transformed");
|
||||
} else if (c.getCurrentStateName().equals(CardStateName.Flipped)) {
|
||||
newText.append("|Flipped");
|
||||
} else if (c.getCurrentStateName().equals(CardStateName.Meld)) {
|
||||
newText.append("|Meld");
|
||||
}
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
if (!counters.isEmpty()) {
|
||||
@@ -169,10 +253,45 @@ public abstract class GameState {
|
||||
newText.append("|Attaching:").append(c.getEnchantingCard().getId());
|
||||
}
|
||||
|
||||
if (!c.getEnchantedBy(false).isEmpty() || !c.getEquippedBy(false).isEmpty() || !c.getFortifiedBy(false).isEmpty()) {
|
||||
newText.append("|Id:").append(c.getId());
|
||||
if (c.getDamage() > 0) {
|
||||
newText.append("|Damage:").append(c.getDamage());
|
||||
}
|
||||
|
||||
if (!c.getChosenColor().isEmpty()) {
|
||||
newText.append("|ChosenColor:").append(TextUtil.join(c.getChosenColors(), ","));
|
||||
}
|
||||
if (!c.getChosenType().isEmpty()) {
|
||||
newText.append("|ChosenType:").append(c.getChosenType());
|
||||
}
|
||||
|
||||
List<String> rememberedCardIds = Lists.newArrayList();
|
||||
for (Object obj : c.getRemembered()) {
|
||||
if (obj instanceof Card) {
|
||||
int id = ((Card)obj).getId();
|
||||
rememberedCardIds.add(String.valueOf(id));
|
||||
}
|
||||
}
|
||||
if (!rememberedCardIds.isEmpty()) {
|
||||
newText.append("|RememberedCards:").append(TextUtil.join(rememberedCardIds, ","));
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneType == ZoneType.Exile) {
|
||||
if (c.getExiledWith() != null) {
|
||||
newText.append("|ExiledWith:").append(c.getExiledWith().getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (c.getGame().getCombat() != null) {
|
||||
if (c.getGame().getCombat().isAttacking(c)) {
|
||||
newText.append("|Attacking");
|
||||
GameEntity def = c.getGame().getCombat().getDefenderByAttacker(c);
|
||||
if (def instanceof Card) {
|
||||
newText.append(":" + def.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cardTexts.put(zoneType, newText.toString());
|
||||
}
|
||||
|
||||
@@ -298,6 +417,12 @@ public abstract class GameState {
|
||||
abilityString.put(categoryName.substring("ability".length()), categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("precast")) {
|
||||
if (isHuman)
|
||||
precastHuman = categoryValue;
|
||||
else
|
||||
precastAI = categoryValue;
|
||||
}
|
||||
else {
|
||||
System.out.println("Unknown key: " + categoryName);
|
||||
}
|
||||
@@ -318,6 +443,13 @@ public abstract class GameState {
|
||||
|
||||
idToCard.clear();
|
||||
cardToAttachId.clear();
|
||||
cardToRememberedId.clear();
|
||||
cardToExiledWithId.clear();
|
||||
markedDamage.clear();
|
||||
cardToChosenClrs.clear();
|
||||
cardToChosenType.clear();
|
||||
cardToScript.clear();
|
||||
cardAttackMap.clear();
|
||||
|
||||
Player newPlayerTurn = tChangePlayer.equals("human") ? human : tChangePlayer.equals("ai") ? ai : null;
|
||||
PhaseType newPhase = tChangePhase.equals("none") ? null : PhaseType.smartValueOf(tChangePhase);
|
||||
@@ -331,20 +463,300 @@ public abstract class GameState {
|
||||
if (!computerCounters.isEmpty()) {
|
||||
applyCountersToGameEntity(ai, computerCounters);
|
||||
}
|
||||
|
||||
game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn);
|
||||
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
game.getTriggerHandler().setSuppressAllTriggers(true);
|
||||
|
||||
setupPlayerState(humanLife, humanCardTexts, human);
|
||||
setupPlayerState(computerLife, aiCardTexts, ai);
|
||||
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
handleCardAttachments();
|
||||
handleChosenEntities();
|
||||
handleRememberedEntities();
|
||||
handleScriptExecution(game);
|
||||
handlePrecastSpells(game);
|
||||
handleMarkedDamage();
|
||||
|
||||
game.getTriggerHandler().setSuppressAllTriggers(false);
|
||||
|
||||
// Combat only works for 1v1 matches for now (which are the only matches dev mode supports anyway)
|
||||
// Note: triggers may fire during combat declarations ("whenever X attacks, ...", etc.)
|
||||
if (newPhase == PhaseType.COMBAT_DECLARE_ATTACKERS || newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||
boolean toDeclareBlockers = newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS;
|
||||
handleCombat(game, newPlayerTurn, newPlayerTurn.getSingleOpponent(), toDeclareBlockers);
|
||||
}
|
||||
|
||||
game.getStack().setResolving(false);
|
||||
|
||||
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
}
|
||||
|
||||
private void handleCombat(final Game game, final Player attackingPlayer, final Player defendingPlayer, final boolean toDeclareBlockers) {
|
||||
// First we need to ensure that all attackers are declared in the Declare Attackers step,
|
||||
// even if proceeding straight to Declare Blockers
|
||||
game.getPhaseHandler().devModeSet(PhaseType.COMBAT_DECLARE_ATTACKERS, attackingPlayer);
|
||||
|
||||
if (game.getPhaseHandler().getCombat() == null) {
|
||||
game.getPhaseHandler().setCombat(new Combat(attackingPlayer));
|
||||
game.updateCombatForView();
|
||||
}
|
||||
|
||||
Combat combat = game.getPhaseHandler().getCombat();
|
||||
for (Entry<Card, Card> attackMap : cardAttackMap.entrySet()) {
|
||||
Card attacker = attackMap.getKey();
|
||||
Card attacked = attackMap.getValue();
|
||||
|
||||
combat.addAttacker(attacker, attacked == null ? defendingPlayer : attacked);
|
||||
}
|
||||
|
||||
// Run the necessary combat events and triggers to set things up correctly as if the
|
||||
// attack was actually declared by the attacking player
|
||||
Multimap<GameEntity, Card> attackersMap = ArrayListMultimap.create();
|
||||
for (GameEntity ge : combat.getDefenders()) {
|
||||
attackersMap.putAll(ge, combat.getAttackersOf(ge));
|
||||
}
|
||||
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
|
||||
|
||||
if (!combat.getAttackers().isEmpty()) {
|
||||
List<GameEntity> attackedTarget = Lists.newArrayList();
|
||||
for (final Card c : combat.getAttackers()) {
|
||||
attackedTarget.add(combat.getDefenderByAttacker(c));
|
||||
}
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Attackers", combat.getAttackers());
|
||||
runParams.put("AttackingPlayer", combat.getAttackingPlayer());
|
||||
runParams.put("AttackedTarget", attackedTarget);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
|
||||
}
|
||||
|
||||
for (final Card c : combat.getAttackers()) {
|
||||
CombatUtil.checkDeclaredAttacker(game, c, combat);
|
||||
}
|
||||
|
||||
game.getTriggerHandler().resetActiveTriggers();
|
||||
game.updateCombatForView();
|
||||
game.fireEvent(new GameEventCombatChanged());
|
||||
|
||||
// Gracefully proceed to Declare Blockers, giving priority to the defending player,
|
||||
// but only if the stack is empty (otherwise the game will crash).
|
||||
game.getStack().addAllTriggeredAbilitiesToStack();
|
||||
if (toDeclareBlockers && game.getStack().isEmpty()) {
|
||||
game.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRememberedEntities() {
|
||||
// Remembered: X
|
||||
for (Entry<Card, List<String>> rememberedEnts : cardToRememberedId.entrySet()) {
|
||||
Card c = rememberedEnts.getKey();
|
||||
List<String> ids = rememberedEnts.getValue();
|
||||
|
||||
for (String id : ids) {
|
||||
Card tgt = idToCard.get(Integer.parseInt(id));
|
||||
c.addRemembered(tgt);
|
||||
}
|
||||
}
|
||||
|
||||
// Exiled with X
|
||||
for (Entry<Card, String> rememberedEnts : cardToExiledWithId.entrySet()) {
|
||||
Card c = rememberedEnts.getKey();
|
||||
String id = rememberedEnts.getValue();
|
||||
|
||||
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
||||
c.setExiledWith(exiledWith);
|
||||
}
|
||||
}
|
||||
|
||||
private int parseTargetInScript(final String tgtDef) {
|
||||
int tgtID = TARGET_NONE;
|
||||
|
||||
if (tgtDef.equalsIgnoreCase("human")) {
|
||||
tgtID = TARGET_HUMAN;
|
||||
} else if (tgtDef.equalsIgnoreCase("ai")) {
|
||||
tgtID = TARGET_AI;
|
||||
} else {
|
||||
tgtID = Integer.parseInt(tgtDef);
|
||||
}
|
||||
|
||||
return tgtID;
|
||||
}
|
||||
|
||||
private void handleScriptedTargetingForSA(final Game game, final SpellAbility sa, int tgtID) {
|
||||
Player human = game.getPlayers().get(0);
|
||||
Player ai = game.getPlayers().get(1);
|
||||
|
||||
if (tgtID != TARGET_NONE) {
|
||||
switch (tgtID) {
|
||||
case TARGET_HUMAN:
|
||||
sa.getTargets().add(human);
|
||||
break;
|
||||
case TARGET_AI:
|
||||
sa.getTargets().add(ai);
|
||||
break;
|
||||
default:
|
||||
sa.getTargets().add(idToCard.get(tgtID));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleScriptExecution(final Game game) {
|
||||
for (Entry<Card, String> scriptPtr : cardToScript.entrySet()) {
|
||||
Card c = scriptPtr.getKey();
|
||||
String sPtr = scriptPtr.getValue();
|
||||
|
||||
executeScript(game, c, sPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeScript(Game game, Card c, String sPtr) {
|
||||
int tgtID = TARGET_NONE;
|
||||
if (sPtr.contains("->")) {
|
||||
String tgtDef = sPtr.substring(sPtr.indexOf("->") + 2);
|
||||
|
||||
tgtID = parseTargetInScript(tgtDef);
|
||||
sPtr = sPtr.substring(0, sPtr.indexOf("->"));
|
||||
}
|
||||
|
||||
SpellAbility sa = null;
|
||||
if (StringUtils.isNumeric(sPtr)) {
|
||||
int numSA = Integer.parseInt(sPtr);
|
||||
if (c.getSpellAbilities().size() >= numSA) {
|
||||
sa = c.getSpellAbilities().get(numSA);
|
||||
} else {
|
||||
System.err.println("ERROR: Unable to find SA with index " + numSA + " on card " + c + " to execute!");
|
||||
}
|
||||
} else {
|
||||
if (!c.hasSVar(sPtr)) {
|
||||
System.err.println("ERROR: Unable to find SVar " + sPtr + " on card " + c + " + to execute!");
|
||||
return;
|
||||
}
|
||||
|
||||
String svarValue = c.getSVar(sPtr);
|
||||
sa = AbilityFactory.getAbility(svarValue, c);
|
||||
if (sa == null) {
|
||||
System.err.println("ERROR: Unable to generate ability for SVar " + svarValue);
|
||||
}
|
||||
}
|
||||
|
||||
sa.setActivatingPlayer(c.getController());
|
||||
handleScriptedTargetingForSA(game, sa, tgtID);
|
||||
|
||||
sa.resolve();
|
||||
}
|
||||
|
||||
private void handlePrecastSpells(final Game game) {
|
||||
Player human = game.getPlayers().get(0);
|
||||
Player ai = game.getPlayers().get(1);
|
||||
|
||||
if (precastHuman != null) {
|
||||
String[] spellList = TextUtil.split(precastHuman, ';');
|
||||
for (String spell : spellList) {
|
||||
precastSpellFromCard(spell, human, game);
|
||||
}
|
||||
}
|
||||
if (precastAI != null) {
|
||||
String[] spellList = TextUtil.split(precastAI, ';');
|
||||
for (String spell : spellList) {
|
||||
precastSpellFromCard(spell, ai, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void precastSpellFromCard(String spellDef, final Player activator, final Game game) {
|
||||
int tgtID = TARGET_NONE;
|
||||
String scriptID = "";
|
||||
|
||||
if (spellDef.contains(":")) {
|
||||
// targeting via -> will be handled in executeScript
|
||||
scriptID = spellDef.substring(spellDef.indexOf(":") + 1);
|
||||
spellDef = spellDef.substring(0, spellDef.indexOf(":"));
|
||||
} else if (spellDef.contains("->")) {
|
||||
String tgtDef = spellDef.substring(spellDef.indexOf("->") + 2);
|
||||
tgtID = parseTargetInScript(tgtDef);
|
||||
spellDef = spellDef.substring(0, spellDef.indexOf("->"));
|
||||
}
|
||||
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef);
|
||||
|
||||
if (pc == null) {
|
||||
System.err.println("ERROR: Could not find a card with name " + spellDef + " to precast!");
|
||||
return;
|
||||
}
|
||||
|
||||
Card c = Card.fromPaperCard(pc, activator);
|
||||
SpellAbility sa = null;
|
||||
|
||||
if (!scriptID.isEmpty()) {
|
||||
executeScript(game, c, scriptID);
|
||||
return;
|
||||
}
|
||||
|
||||
sa = c.getFirstSpellAbility();
|
||||
sa.setActivatingPlayer(activator);
|
||||
|
||||
handleScriptedTargetingForSA(game, sa, tgtID);
|
||||
|
||||
sa.resolve();
|
||||
}
|
||||
|
||||
private void handleMarkedDamage() {
|
||||
for (Entry<Card, Integer> entry : markedDamage.entrySet()) {
|
||||
Card c = entry.getKey();
|
||||
Integer dmg = entry.getValue();
|
||||
|
||||
c.setDamage(dmg);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChosenEntities() {
|
||||
// TODO: the AI still gets to choose something (and the notification box pops up) before the
|
||||
// choice is overwritten here. Somehow improve this so that there is at least no notification
|
||||
// about the choice that will be force-changed anyway.
|
||||
|
||||
// Chosen colors
|
||||
for (Entry<Card, List<String>> entry : cardToChosenClrs.entrySet()) {
|
||||
Card c = entry.getKey();
|
||||
List<String> colors = entry.getValue();
|
||||
|
||||
c.setChosenColors(colors);
|
||||
}
|
||||
|
||||
// Chosen type
|
||||
for (Entry<Card, String> entry : cardToChosenType.entrySet()) {
|
||||
Card c = entry.getKey();
|
||||
c.setChosenType(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCardAttachments() {
|
||||
// Unattach all permanents first
|
||||
for(Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
|
||||
Card attachedTo = idToCard.get(entry.getValue());
|
||||
|
||||
attachedTo.unEnchantAllCards();
|
||||
attachedTo.unEquipAllCards();
|
||||
for (Card c : attachedTo.getFortifiedBy(true)) {
|
||||
attachedTo.unFortifyCard(c);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach permanents by ID
|
||||
for(Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
|
||||
Card attachedTo = idToCard.get(entry.getValue());
|
||||
Card attacher = entry.getKey();
|
||||
|
||||
if (attacher.isEquipment()) {
|
||||
attacher.equipCard(attachedTo);
|
||||
} else if (attacher.isAura()) {
|
||||
attacher.enchantEntity(attachedTo);
|
||||
} else if (attacher.isFortified()) {
|
||||
attacher.fortifyCard(attachedTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
||||
//entity.setCounters(new HashMap<CounterType, Integer>());
|
||||
String[] allCounterStrings = counterString.split(",");
|
||||
@@ -356,7 +768,6 @@ public abstract class GameState {
|
||||
|
||||
private void setupPlayerState(int life, Map<ZoneType, String> cardTexts, final Player p) {
|
||||
// Lock check static as we setup player state
|
||||
final Game game = p.getGame();
|
||||
|
||||
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<ZoneType, CardCollectionView>(ZoneType.class);
|
||||
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
|
||||
@@ -384,9 +795,13 @@ public abstract class GameState {
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
// Note: Not clearCounters() since we want to keep the counters
|
||||
// var as-is.
|
||||
c.setCounters(new HashMap<CounterType, Integer>());
|
||||
c.setCounters(Maps.<CounterType, Integer>newEnumMap(CounterType.class));
|
||||
p.getZone(ZoneType.Hand).add(c);
|
||||
if (c.isAura()) {
|
||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
||||
// (will be overridden later, so the actual value shouldn't matter)
|
||||
c.setEnchanting(c);
|
||||
|
||||
p.getGame().getAction().moveToPlay(c, null);
|
||||
} else {
|
||||
p.getGame().getAction().moveToPlay(c, null);
|
||||
@@ -401,25 +816,6 @@ public abstract class GameState {
|
||||
}
|
||||
}
|
||||
|
||||
game.getTriggerHandler().suppressMode(TriggerType.Unequip);
|
||||
|
||||
for(Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
|
||||
Card attachedTo = idToCard.get(entry.getValue());
|
||||
Card attacher = entry.getKey();
|
||||
|
||||
attachedTo.unEnchantAllCards();
|
||||
attachedTo.unEquipAllCards();
|
||||
|
||||
if (attacher.isEquipment()) {
|
||||
attacher.equipCard(attachedTo);
|
||||
} else if (attacher.isAura()) {
|
||||
attacher.enchantEntity(attachedTo);
|
||||
} else if (attacher.isFortified()) {
|
||||
attacher.fortifyCard(attachedTo);
|
||||
}
|
||||
}
|
||||
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.Unequip);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -453,6 +849,11 @@ public abstract class GameState {
|
||||
c = CardFactory.makeOneToken(CardFactory.TokenInfo.fromString(tokenStr), player);
|
||||
} else {
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(cardinfo[0], setCode);
|
||||
if (pc == null) {
|
||||
System.err.println("ERROR: Tried to create a non-existent card named " + cardinfo[0] + " (set: " + (setCode == null ? "any" : setCode) + ") when loading game state!");
|
||||
continue;
|
||||
}
|
||||
|
||||
c = Card.fromPaperCard(pc, player);
|
||||
if (setCode != null) {
|
||||
hasSetCurSet = true;
|
||||
@@ -463,6 +864,13 @@ public abstract class GameState {
|
||||
for (final String info : cardinfo) {
|
||||
if (info.startsWith("Tapped")) {
|
||||
c.tap();
|
||||
} else if (info.startsWith("Renowned")) {
|
||||
c.setRenowned(true);
|
||||
} else if (info.startsWith("Monstrous:")) {
|
||||
c.setMonstrous(true);
|
||||
c.setMonstrosityNum(Integer.parseInt(info.substring((info.indexOf(':') + 1))));
|
||||
} else if (info.startsWith("PhasedOut")) {
|
||||
c.setPhasedOut(true);
|
||||
} else if (info.startsWith("Counters:")) {
|
||||
applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("SummonSick")) {
|
||||
@@ -474,6 +882,10 @@ public abstract class GameState {
|
||||
}
|
||||
} else if (info.startsWith("Transformed")) {
|
||||
c.setState(CardStateName.Transformed, true);
|
||||
} else if (info.startsWith("Flipped")) {
|
||||
c.setState(CardStateName.Flipped, true);
|
||||
} else if (info.startsWith("Meld")) {
|
||||
c.setState(CardStateName.Meld, true);
|
||||
} else if (info.startsWith("IsCommander")) {
|
||||
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
|
||||
c.setCommander(true);
|
||||
@@ -488,6 +900,26 @@ public abstract class GameState {
|
||||
} else if (info.startsWith("Ability:")) {
|
||||
String abString = info.substring(info.indexOf(':') + 1).toLowerCase();
|
||||
c.addSpellAbility(AbilityFactory.getAbility(abilityString.get(abString), c));
|
||||
} else if (info.startsWith("Damage:")) {
|
||||
int dmg = Integer.parseInt(info.substring(info.indexOf(':') + 1));
|
||||
markedDamage.put(c, dmg);
|
||||
} else if (info.startsWith("ChosenColor:")) {
|
||||
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
||||
} else if (info.startsWith("ChosenType:")) {
|
||||
cardToChosenType.put(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("ExecuteScript:")) {
|
||||
cardToScript.put(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("RememberedCards:")) {
|
||||
cardToRememberedId.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
||||
} else if (info.startsWith("ExiledWith:")) {
|
||||
cardToExiledWithId.put(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("Attacking")) {
|
||||
if (info.contains(":")) {
|
||||
int id = Integer.parseInt(info.substring(info.indexOf(':') + 1));
|
||||
cardAttackMap.put(c, idToCard.get(id));
|
||||
} else {
|
||||
cardAttackMap.put(c, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package forge.ai;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import forge.AIOption;
|
||||
import forge.LobbyPlayer;
|
||||
import forge.game.Game;
|
||||
import forge.game.player.IGameEntitiesFactory;
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.*;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -46,7 +47,6 @@ import forge.game.player.PlayerController;
|
||||
import forge.game.player.PlayerView;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
@@ -115,7 +115,23 @@ public class PlayerControllerAi extends PlayerController {
|
||||
if (ability.getApi() != null) {
|
||||
switch (ability.getApi()) {
|
||||
case ChooseNumber:
|
||||
return ability.getActivatingPlayer().isOpponentOf(player) ? 0 : ComputerUtilMana.determineLeftoverMana(ability, player);
|
||||
Player payingPlayer = ability.getActivatingPlayer();
|
||||
String logic = ability.getParamOrDefault("AILogic", "");
|
||||
boolean anyController = logic.equals("MaxForAnyController");
|
||||
|
||||
if (logic.startsWith("PowerLeakMaxMana.") && ability.getHostCard().isEnchantingCard()) {
|
||||
// For cards like Power Leak, the payer will be the owner of the enchanted card
|
||||
// TODO: is there any way to generalize this and avoid a special exclusion?
|
||||
payingPlayer = ability.getHostCard().getEnchantingCard().getController();
|
||||
}
|
||||
|
||||
int number = ComputerUtilMana.determineLeftoverMana(ability, player);
|
||||
|
||||
if (logic.startsWith("MaxMana.") || logic.startsWith("PowerLeakMaxMana.")) {
|
||||
number = Math.min(number, Integer.parseInt(logic.substring(logic.indexOf(".") + 1)));
|
||||
}
|
||||
|
||||
return payingPlayer.isOpponentOf(player) && !anyController ? 0 : number;
|
||||
case BidLife:
|
||||
return 0;
|
||||
default:
|
||||
@@ -179,8 +195,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public boolean confirmTrigger(WrappedAbility wrapper, Map<String, String> triggerParams, boolean isMandatory) {
|
||||
final SpellAbility sa = wrapper.getWrappedAbility();
|
||||
final Trigger regtrig = wrapper.getTrigger();
|
||||
final SpellAbility sa = wrapper.getWrappedAbility();
|
||||
//final Trigger regtrig = wrapper.getTrigger();
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
||||
return true;
|
||||
}
|
||||
@@ -844,20 +860,37 @@ public class PlayerControllerAi extends PlayerController {
|
||||
@Override
|
||||
public String chooseCardName(SpellAbility sa, Predicate<ICardFace> cpp, String valid, String message) {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
CardCollectionView aiLibrary = player.getCardsIn(ZoneType.Library);
|
||||
CardCollectionView oppLibrary = ComputerUtil.getOpponentFor(player).getCardsIn(ZoneType.Library);
|
||||
final Card source = sa.getHostCard();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
|
||||
if (source != null && source.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda")) {
|
||||
// If any Conspiracies are present, try not to choose the same name twice
|
||||
// (otherwise the AI will spam the same name)
|
||||
for (Card consp : player.getCardsIn(ZoneType.Command)) {
|
||||
if (consp.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda")) {
|
||||
String chosenName = consp.getNamedCard();
|
||||
if (!chosenName.isEmpty()) {
|
||||
aiLibrary = CardLists.filter(aiLibrary, Predicates.not(CardPredicates.nameEquals(chosenName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logic.equals("MostProminentInComputerDeck")) {
|
||||
return ComputerUtilCard.getMostProminentCardName(player.getCardsIn(ZoneType.Library));
|
||||
return ComputerUtilCard.getMostProminentCardName(aiLibrary);
|
||||
} else if (logic.equals("MostProminentInHumanDeck")) {
|
||||
return ComputerUtilCard.getMostProminentCardName(player.getOpponent().getCardsIn(ZoneType.Library));
|
||||
return ComputerUtilCard.getMostProminentCardName(oppLibrary);
|
||||
} else if (logic.equals("MostProminentCreatureInComputerDeck")) {
|
||||
CardCollectionView cards = CardLists.getValidCards(player.getCardsIn(ZoneType.Library), "Creature", player, sa.getHostCard());
|
||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Creature", player, sa.getHostCard());
|
||||
return ComputerUtilCard.getMostProminentCardName(cards);
|
||||
} else if (logic.equals("BestCreatureInComputerDeck")) {
|
||||
return ComputerUtilCard.getBestCreatureAI(player.getCardsIn(ZoneType.Library)).getName();
|
||||
return ComputerUtilCard.getBestCreatureAI(aiLibrary).getName();
|
||||
} else if (logic.equals("RandomInComputerDeck")) {
|
||||
return Aggregates.random(player.getCardsIn(ZoneType.Library)).getName();
|
||||
return Aggregates.random(aiLibrary).getName();
|
||||
} else if (logic.equals("MostProminentSpellInComputerDeck")) {
|
||||
CardCollectionView cards = CardLists.getValidCards(player.getCardsIn(ZoneType.Library), "Card.Instant,Card.Sorcery", player, sa.getHostCard());
|
||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Card.Instant,Card.Sorcery", player, sa.getHostCard());
|
||||
return ComputerUtilCard.getMostProminentCardName(cards);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -17,28 +17,17 @@
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
@@ -49,6 +38,10 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Special logic for individual cards
|
||||
*
|
||||
@@ -139,7 +132,23 @@ public class SpecialCardAi {
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Chain of Acid
|
||||
public static class ChainOfAcid {
|
||||
public static boolean consider(Player ai, SpellAbility sa) {
|
||||
List<Card> AiLandsOnly = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.Presets.LANDS);
|
||||
List<Card> OppPerms = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield),
|
||||
Predicates.not(CardPredicates.Presets.CREATURES));
|
||||
|
||||
// TODO: improve this logic (currently the AI has difficulty evaluating non-creature permanents,
|
||||
// which it can only distinguish by their CMC, considering >CMC higher value).
|
||||
// Currently ensures that the AI will still have lands provided that the human player goes to
|
||||
// destroy all the AI's lands in order (to avoid manalock).
|
||||
return !OppPerms.isEmpty() && AiLandsOnly.size() > OppPerms.size() + 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Chain of Smog
|
||||
public static class ChainOfSmog {
|
||||
public static boolean consider(Player ai, SpellAbility sa) {
|
||||
@@ -351,7 +360,35 @@ public class SpecialCardAi {
|
||||
return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Mairsil, the Pretender
|
||||
public static class MairsilThePretender {
|
||||
// Scan the fetch list for a card with at least one activated ability.
|
||||
// TODO: can be improved to a full consider(sa, ai) logic which would scan the graveyard first and hand last
|
||||
public static Card considerCardFromList(CardCollection fetchList) {
|
||||
for (Card c : CardLists.filter(fetchList, Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.CREATURES))) {
|
||||
for (SpellAbility ab : c.getSpellAbilities()) {
|
||||
if (ab.isAbility() && !ab.isTrigger()) {
|
||||
Player controller = c.getController();
|
||||
boolean wasCaged = false;
|
||||
for (Card caged : CardLists.filter(controller.getCardsIn(ZoneType.Exile),
|
||||
CardPredicates.hasCounter(CounterType.CAGE))) {
|
||||
if (c.getName().equals(caged.getName())) {
|
||||
wasCaged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasCaged) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Necropotence
|
||||
public static class Necropotence {
|
||||
public static boolean consider(Player ai, SpellAbility sa) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -21,7 +22,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
@@ -46,7 +47,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
@@ -87,7 +88,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AlwaysPlayAi extends SpellAbilityAi {
|
||||
@@ -13,4 +14,9 @@ public class AlwaysPlayAi extends SpellAbilityAi {
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.ai.AiCardMemory;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -86,13 +86,13 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
num = (num == null) ? "1" : num;
|
||||
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
ai.getOpponent(), topStack.getHostCard(), topStack);
|
||||
ComputerUtil.getOpponentFor(ai), topStack.getHostCard(), topStack);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
||||
ComputerUtilCard.sortByEvaluateCreature(list);
|
||||
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai)) {
|
||||
Card animatedCopy = becomeAnimated(source, sa);
|
||||
list.add(animatedCopy);
|
||||
list = CardLists.getValidCards(list, valid.split(","), ai.getOpponent(), topStack.getHostCard(),
|
||||
list = CardLists.getValidCards(list, valid.split(","), ComputerUtil.getOpponentFor(ai), topStack.getHostCard(),
|
||||
topStack);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
||||
if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0))
|
||||
|
||||
@@ -580,8 +580,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
continue;
|
||||
}
|
||||
if ((affected.contains(stCheck) || affected.contains("AttachedBy"))) {
|
||||
totToughness += AttachAi.parseSVar(attachSource, stabMap.get("AddToughness"));
|
||||
totPower += AttachAi.parseSVar(attachSource, stabMap.get("AddPower"));
|
||||
totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), sa);
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), sa);
|
||||
|
||||
String kws = stabMap.get("AddKeyword");
|
||||
if (kws != null) {
|
||||
@@ -702,30 +702,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseSVar TODO - flesh out javadoc for this method.
|
||||
*
|
||||
* @param hostCard
|
||||
* the Card with the SVar on it
|
||||
* @param amount
|
||||
* a String
|
||||
* @return the calculated number
|
||||
*/
|
||||
public static int parseSVar(final Card hostCard, final String amount) {
|
||||
int num = 0;
|
||||
if (amount == null) {
|
||||
return num;
|
||||
}
|
||||
|
||||
try {
|
||||
num = Integer.valueOf(amount);
|
||||
} catch (final NumberFormatException e) {
|
||||
num = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(amount).split("\\$")[1]);
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach preference.
|
||||
*
|
||||
@@ -874,8 +850,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
continue;
|
||||
}
|
||||
if ((affected.contains(stCheck) || affected.contains("AttachedBy"))) {
|
||||
totToughness += AttachAi.parseSVar(attachSource, stabMap.get("AddToughness"));
|
||||
totPower += AttachAi.parseSVar(attachSource, stabMap.get("AddPower"));
|
||||
totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), sa);
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), sa);
|
||||
|
||||
grantingAbilities |= stabMap.containsKey("AddAbility");
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
@@ -16,7 +17,7 @@ public class BalanceAi extends SpellAbilityAi {
|
||||
|
||||
int diff = 0;
|
||||
// TODO Add support for multiplayer logic
|
||||
final Player opp = aiPlayer.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canTgtCreature()) {
|
||||
List<Card> list = CardLists.getTargetableCards(aiPlayer.getOpponent().getCardsIn(ZoneType.Battlefield), sa);
|
||||
List<Card> list = CardLists.getTargetableCards(ComputerUtil.getOpponentFor(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.card.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
@@ -22,11 +23,6 @@ import forge.game.GameObject;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -166,7 +162,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
ZoneType origin = null;
|
||||
final Player opponent = ai.getOpponent();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
|
||||
if (sa.hasParam("Origin")) {
|
||||
@@ -367,7 +363,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// if putting cards from hand to library and parent is drawing cards
|
||||
// make sure this will actually do something:
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = aiPlayer.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
if (tgt != null && tgt.canTgtPlayer()) {
|
||||
boolean isCurse = sa.isCurse();
|
||||
if (isCurse && sa.canTarget(opp)) {
|
||||
@@ -428,7 +424,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
Iterable<Player> pDefined;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.isCurse()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
@@ -547,8 +543,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
*/
|
||||
private static Card chooseCreature(final Player ai, CardCollection list) {
|
||||
// Creating a new combat for testing purposes.
|
||||
Combat combat = new Combat(ai.getOpponent());
|
||||
for (Card att : ai.getOpponent().getCreaturesInPlay()) {
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
Combat combat = new Combat(opponent);
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
combat.addAttacker(att, ai);
|
||||
}
|
||||
AiBlockController block = new AiBlockController(ai);
|
||||
@@ -647,6 +644,20 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (destination == ZoneType.Battlefield) {
|
||||
// predict whether something may put a ETBing creature below zero toughness
|
||||
// (e.g. Reassembing Skeleton + Elesh Norn, Grand Cenobite)
|
||||
for (final Card c : retrieval) {
|
||||
if (c.isCreature()) {
|
||||
final Card copy = CardUtil.getLKICopy(c);
|
||||
ComputerUtilCard.applyStaticContPT(c.getGame(), copy, null);
|
||||
if (copy.getNetToughness() <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
@@ -828,7 +839,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
&& !currCombat.getBlockers(attacker).isEmpty()) {
|
||||
ComputerUtilCard.sortByEvaluateCreature(blockers);
|
||||
Combat combat = new Combat(ai);
|
||||
combat.addAttacker(attacker, ai.getOpponent());
|
||||
combat.addAttacker(attacker, ComputerUtil.getOpponentFor(ai));
|
||||
for (Card blocker : blockers) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
}
|
||||
@@ -1319,6 +1330,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
} else if ("WorstCard".equals(logic)) {
|
||||
return ComputerUtilCard.getWorstAI(fetchList);
|
||||
} else if ("Mairsil".equals(logic)) {
|
||||
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
|
||||
}
|
||||
}
|
||||
if (fetchList.isEmpty()) {
|
||||
|
||||
@@ -73,14 +73,15 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||
|
||||
// Living Death AI
|
||||
if ("LivingDeath".equals(sa.getParam("AILogic"))) {
|
||||
// Living Death AI
|
||||
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
||||
}
|
||||
|
||||
// Timetwister AI
|
||||
if ("Timetwister".equals(sa.getParam("AILogic"))) {
|
||||
} else if ("Timetwister".equals(sa.getParam("AILogic"))) {
|
||||
// Timetwister AI
|
||||
return SpecialCardAi.Timetwister.consider(ai, sa);
|
||||
} else if ("RetDiscardedThisTurn".equals(sa.getParam("AILogic"))) {
|
||||
// e.g. Shadow of the Grave
|
||||
return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
// TODO improve restrictions on when the AI would want to use this
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.List;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -122,7 +124,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
}
|
||||
} else if (aiLogic.equals("Duneblast")) {
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
|
||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, "Indestructible");
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -53,7 +52,7 @@ public class ChooseColorAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if ("Addle".equals(sourceName)) {
|
||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ai.getOpponent().getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -62,7 +61,7 @@ public class ChooseColorAi extends SpellAbilityAi {
|
||||
if (logic.equals("MostExcessOpponentControls")) {
|
||||
for (byte color : MagicColor.WUBRG) {
|
||||
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
|
||||
CardCollectionView opplist = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
CardCollectionView opplist = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
|
||||
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -16,8 +17,9 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(aiPlayer.getOpponent())) {
|
||||
sa.getTargets().add(aiPlayer.getOpponent());
|
||||
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
@@ -63,8 +64,9 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(ai.getOpponent())) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list =
|
||||
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
// AI won't try to grab cards that are filtered out of AI decks on
|
||||
// purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
@@ -25,9 +26,10 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||
// NOTE: Other SAs that use CopySpellAbilityAi (e.g. Chain Lightning) are currently routed through
|
||||
// generic method SpellAbilityAi#chkDrawbackWithSubs and are handled there.
|
||||
|
||||
if ("ChainOfSmog".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.ChainOfSmog.consider(aiPlayer, sa);
|
||||
} else if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.ChainOfAcid.consider(aiPlayer, sa);
|
||||
}
|
||||
|
||||
return super.chkAIDrawback(sa, aiPlayer);
|
||||
@@ -37,5 +39,17 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// Chain of Acid requires special attention here since otherwise the AI will confirm the copy and then
|
||||
// run into the necessity of confirming a mandatory Destroy, thus destroying all of its own permanents.
|
||||
if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
|
||||
return SpecialCardAi.ChainOfAcid.consider(player, sa);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import java.util.Iterator;
|
||||
|
||||
@@ -94,7 +95,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
if (unlessCost != null && !unlessCost.endsWith(">")) {
|
||||
// Is this Usable Mana Sources? Or Total Available Mana?
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ComputerUtil.getOpponentFor(ai), true).size();
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
@@ -213,7 +214,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
if (unlessCost != null) {
|
||||
// Is this Usable Mana Sources? Or Total Available Mana?
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ComputerUtil.getOpponentFor(ai), true).size();
|
||||
int toPay = 0;
|
||||
boolean setPayX = false;
|
||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -373,6 +374,13 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
});
|
||||
|
||||
if (abCost.hasSpecificCostType(CostSacrifice.class)) {
|
||||
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
|
||||
// this card is planned to be sacrificed during cost payment, so don't target it
|
||||
// (otherwise the AI can cheat by activating this SA and not paying the sac cost, e.g. Extruder)
|
||||
list.remove(sacTarget);
|
||||
}
|
||||
|
||||
if (list.size() < sa.getTargetRestrictions().getMinTargets(source, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -35,10 +37,11 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String valid = sa.getParam("ValidCards");
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
final boolean curse = sa.isCurse();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
hList = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
hList = CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
|
||||
if (abCost != null) {
|
||||
@@ -56,8 +59,14 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (logic.equals("AtEOTOrBlock")) {
|
||||
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tgt != null) {
|
||||
Player pl = curse ? ai.getOpponent() : ai;
|
||||
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
|
||||
sa.getTargets().add(pl);
|
||||
|
||||
hList = CardLists.filterControlledBy(hList, pl);
|
||||
@@ -138,6 +147,6 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return player.getCreaturesInPlay().size() >= player.getOpponent().getCreaturesInPlay().size();
|
||||
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -18,7 +19,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
||||
int restDamage = d;
|
||||
final Game game = comp.getGame();
|
||||
final Player enemy = comp.getOpponent();
|
||||
final Player enemy = ComputerUtil.getOpponentFor(comp);
|
||||
if (!sa.canTarget(enemy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
return evaluateDamageAll(ai, sa, source, dmg) > 0;
|
||||
} else {
|
||||
int best = -1, best_x = -1;
|
||||
for (int i = 0; i < x; i++) {
|
||||
for (int i = 0; i <= x; i++) {
|
||||
final int value = evaluateDamageAll(ai, sa, source, i);
|
||||
if (value > best) {
|
||||
best = value;
|
||||
@@ -80,7 +80,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) {
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
|
||||
@@ -179,7 +179,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Evaluate creatures getting killed
|
||||
Player enemy = ai.getOpponent();
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
@@ -261,7 +261,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Evaluate creatures getting killed
|
||||
Player enemy = ai.getOpponent();
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
@@ -345,7 +345,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
||||
|
||||
Player enemy = ai.getOpponent();
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
||||
// check if it is better to target the player instead, the original target is already set in PumpAi.pumpTgtAI()
|
||||
@@ -377,7 +377,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
if ("Polukranos".equals(sa.getParam("AILogic"))) {
|
||||
int dmgTaken = 0;
|
||||
CardCollection humCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||
CardCollection humCreatures = enemy.getCreaturesInPlay();
|
||||
Card lastTgt = null;
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||
@@ -633,7 +633,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
// this is for Triggered targets that are mandatory
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
System.out.println("damageChooseRequiredTargets " + ai + " " + sa);
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
|
||||
@@ -3,10 +3,6 @@ package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -176,7 +177,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
* @return a CardCollection.
|
||||
*/
|
||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@@ -216,7 +217,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponent());
|
||||
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
|
||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
} else if (sa.hasParam("Defined")) {
|
||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|
||||
|| ai.getCreaturesInPlay().size() < ai.getOpponent().getCreaturesInPlay().size()
|
||||
|| ai.getCreaturesInPlay().size() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|
||||
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
||||
|| ai.getLife() <= 5)) {
|
||||
// Basic ai logic for Lethal Vapors
|
||||
|
||||
@@ -64,7 +64,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
Player opponent = ai.getOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
||||
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
||||
|
||||
final int CREATURE_EVAL_THRESHOLD = 200;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
@@ -26,7 +25,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
@@ -103,7 +102,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
@@ -29,7 +30,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
Player libraryOwner = ai;
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
|
||||
@@ -53,7 +53,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
|
||||
}
|
||||
|
||||
final boolean humanHasHand = ai.getOpponent().getCardsIn(ZoneType.Hand).size() > 0;
|
||||
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
|
||||
|
||||
if (tgt != null) {
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
@@ -84,7 +84,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
if (sa.hasParam("NumCards")) {
|
||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
if (cardsToDiscard < 1) {
|
||||
return false;
|
||||
@@ -120,7 +120,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
|
||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
@@ -139,7 +139,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
@@ -160,7 +160,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -19,7 +20,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
@@ -42,7 +43,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
@@ -83,7 +84,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
|
||||
@@ -16,6 +16,7 @@ import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
@@ -179,9 +180,7 @@ public class EffectAi extends SpellAbilityAi {
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldPlay) {
|
||||
return true;
|
||||
}
|
||||
return shouldPlay;
|
||||
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
@@ -246,6 +245,12 @@ public class EffectAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (logic.equals("CastFromGraveThisTurn")) {
|
||||
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
|
||||
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else { //no AILogic
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -58,7 +59,7 @@ public class FogAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
final Game game = aiPlayer.getGame();
|
||||
boolean chance;
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer().getOpponent())) {
|
||||
if (game.getPhaseHandler().isPlayerTurn(ComputerUtil.getOpponentFor(sa.getActivatingPlayer()))) {
|
||||
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
||||
} else {
|
||||
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -8,7 +9,7 @@ import forge.game.spellability.TargetRestrictions;
|
||||
public class GameLossAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (opp.cantLose()) {
|
||||
return false;
|
||||
}
|
||||
@@ -34,14 +35,14 @@ public class GameLossAi extends SpellAbilityAi {
|
||||
// (Final Fortune would need to attach it's delayed trigger to a
|
||||
// specific turn, which can't be done yet)
|
||||
|
||||
if (!mandatory && ai.getOpponent().cantLose()) {
|
||||
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -22,7 +23,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Random r = MyRandom.getRandom();
|
||||
final int myLife = aiPlayer.getLife();
|
||||
Player opponent = aiPlayer.getOpponent();
|
||||
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
final int hLife = opponent.getLife();
|
||||
|
||||
if (!aiPlayer.canGainLife()) {
|
||||
@@ -78,7 +79,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
final boolean mandatory) {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -22,7 +23,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
// Ability_Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getOpponent();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
final int hlife = opponent.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
@@ -109,7 +110,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getOpponent();
|
||||
final Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
final int hlife = opponent.getLife();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -60,7 +62,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
||||
boolean chance = false;
|
||||
|
||||
if (abTgt != null) {
|
||||
List<Card> list = CardLists.filter(ai.getOpponent().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
List<Card> list = CardLists.filter(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@@ -99,7 +99,8 @@ public class PlayAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options,
|
||||
final boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -31,7 +32,7 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
List<Card> list =
|
||||
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
// AI won't try to grab cards that are filtered out of AI decks on
|
||||
// purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@@ -147,9 +147,10 @@ public class ProtectAi extends SpellAbilityAi {
|
||||
if (s==null) {
|
||||
return false;
|
||||
} else {
|
||||
Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
int dmg = ComputerUtilCombat.damageIfUnblocked(c, ai.getOpponent(), combat, true);
|
||||
float ratio = 1.0f * dmg / ai.getOpponent().getLife();
|
||||
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opponent, combat, true);
|
||||
float ratio = 1.0f * dmg / opponent.getLife();
|
||||
Random r = MyRandom.getRandom();
|
||||
return r.nextFloat() < ratio;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
@@ -461,6 +462,22 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Detain target nonland permanent: don't target noncreature permanents that don't have
|
||||
// any activated abilities.
|
||||
if ("DetainNonLand".equals(sa.getParam("AILogic"))) {
|
||||
list = CardLists.filter(list, Predicates.or(CardPredicates.Presets.CREATURES, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
for (SpellAbility sa: card.getSpellAbilities()) {
|
||||
if (sa.isAbility()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if (ComputerUtil.activateForCost(sa, ai)) {
|
||||
return pumpMandatoryTarget(ai, sa);
|
||||
|
||||
@@ -181,7 +181,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final int newPower = card.getNetCombatDamage() + attack;
|
||||
//int defense = getNumDefense(sa);
|
||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
||||
|
||||
@@ -57,7 +57,7 @@ public class PumpAllAi extends PumpAiBase {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -27,7 +28,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
|
||||
// ability is targeted
|
||||
sa.resetTargets();
|
||||
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final boolean canTgtHuman = opp.canBeTargetedBy(sa);
|
||||
|
||||
if (!canTgtHuman) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -17,7 +18,7 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if (tgt != null) {
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
@@ -48,7 +49,7 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -59,7 +60,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final boolean destroy = sa.hasParam("Destroy");
|
||||
|
||||
Player opp = ai.getOpponent();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
@@ -72,7 +73,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa);
|
||||
|
||||
List<Card> list =
|
||||
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
for (Card c : list) {
|
||||
if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) {
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
@@ -38,7 +39,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
CardCollection humanlist =
|
||||
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
CardCollection computerlist =
|
||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardSplitType;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -172,6 +167,18 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
|
||||
private boolean shouldTurnFace(Card card, Player ai, PhaseHandler ph) {
|
||||
if (card.isFaceDown()) {
|
||||
// hidden agenda
|
||||
if (card.getState(CardStateName.Original).hasIntrinsicKeyword("Hidden agenda")
|
||||
&& card.getZone().is(ZoneType.Command)) {
|
||||
String chosenName = card.getNamedCard();
|
||||
for (Card cast : ai.getGame().getStack().getSpellsCastThisTurn()) {
|
||||
if (cast.getController() == ai && cast.getName().equals(chosenName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// non-permanent facedown can't be turned face up
|
||||
if (!card.getRules().getType().isPermanent()) {
|
||||
return false;
|
||||
|
||||
@@ -111,7 +111,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) {
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Game game = ai.getGame();
|
||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -30,7 +31,7 @@ public class TapAllAi extends SpellAbilityAi {
|
||||
// or during upkeep/begin combat?
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if (game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) {
|
||||
@@ -126,8 +127,8 @@ public class TapAllAi extends SpellAbilityAi {
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
validTappables = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
validTappables = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
}
|
||||
|
||||
if (mandatory) {
|
||||
|
||||
@@ -179,7 +179,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
*/
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevent infinite tokens?
|
||||
@@ -254,13 +254,13 @@ public class TokenAi extends SpellAbilityAi {
|
||||
num = (num == null) ? "1" : num;
|
||||
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
ai.getOpponent(), topStack.getHostCard(), sa);
|
||||
ComputerUtil.getOpponentFor(ai), topStack.getHostCard(), sa);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
||||
// only care about saving single creature for now
|
||||
if (!list.isEmpty() && nTokens > 0 && list.size() == nToSac) {
|
||||
ComputerUtilCard.sortByEvaluateCreature(list);
|
||||
list.add(token);
|
||||
list = CardLists.getValidCards(list, valid.split(","), ai.getOpponent(), topStack.getHostCard(), sa);
|
||||
list = CardLists.getValidCards(list, valid.split(","), ComputerUtil.getOpponentFor(ai), topStack.getHostCard(), sa);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
|
||||
if (ComputerUtilCard.evaluateCreature(token) < ComputerUtilCard.evaluateCreature(list.get(0))
|
||||
&& list.contains(token)) {
|
||||
@@ -278,7 +278,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -66,7 +67,7 @@ public class UnattachAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
// Check if there are any valid targets
|
||||
List<GameObject> targets = new ArrayList<GameObject>();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
@@ -13,6 +11,7 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostTap;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
@@ -20,6 +19,8 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class UntapAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
@@ -132,7 +133,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
Player targetController = ai;
|
||||
|
||||
if (sa.isCurse()) {
|
||||
targetController = ai.getOpponent();
|
||||
targetController = ComputerUtil.getOpponentFor(ai);
|
||||
}
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
|
||||
@@ -148,6 +149,24 @@ public class UntapAi extends SpellAbilityAi {
|
||||
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
||||
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
||||
|
||||
// Try to avoid potential infinite recursion,
|
||||
// e.g. Kiora's Follower untapping another Kiora's Follower and repeating infinitely
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
|
||||
CardCollection toRemove = new CardCollection();
|
||||
for (Card c : untapList) {
|
||||
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
||||
if (ab.getApi() == ApiType.Untap
|
||||
&& ab.getPayCosts() != null
|
||||
&& ab.getPayCosts().hasOnlySpecificCostType(CostTap.class)
|
||||
&& ab.canTarget(source)) {
|
||||
toRemove.add(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
untapList.removeAll(toRemove);
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card choice = null;
|
||||
|
||||
@@ -303,7 +303,7 @@ public class GameCopier {
|
||||
newCard.setSemiPermanentToughnessBoost(c.getSemiPermanentToughnessBoost());
|
||||
newCard.setDamage(c.getDamage());
|
||||
|
||||
newCard.setChangedCardTypes(c.getChangedCardTypes());
|
||||
newCard.setChangedCardTypes(c.getChangedCardTypesMap());
|
||||
newCard.setChangedCardKeywords(c.getChangedCardKeywords());
|
||||
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
|
||||
for (String kw : c.getHiddenExtrinsicKeywords())
|
||||
|
||||
@@ -204,7 +204,7 @@ public class GameSimulator {
|
||||
}
|
||||
|
||||
// TODO: Support multiple opponents.
|
||||
Player opponent = aiPlayer.getOpponent();
|
||||
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
|
||||
resolveStack(simGame, opponent);
|
||||
|
||||
// TODO: If this is during combat, before blockers are declared,
|
||||
|
||||
@@ -4,7 +4,6 @@ import forge.ai.CreatureEvaluator;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.1</version>
|
||||
<version>1.6.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -411,11 +411,13 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardTypeView getTypeWithChanges(final Map<Long, CardChangedType> changedCardTypes) {
|
||||
if (changedCardTypes.isEmpty()) { return this; }
|
||||
public CardTypeView getTypeWithChanges(final Iterable<CardChangedType> changedCardTypes) {
|
||||
CardType newType = null;
|
||||
// we assume that changes are already correctly ordered (taken from TreeMap.values())
|
||||
for (final CardChangedType ct : changedCardTypes) {
|
||||
if(null == newType)
|
||||
newType = new CardType(CardType.this);
|
||||
|
||||
final CardType newType = new CardType(CardType.this);
|
||||
for (final CardChangedType ct : changedCardTypes.values()) {
|
||||
if (ct.isRemoveCardTypes()) {
|
||||
newType.coreTypes.clear();
|
||||
}
|
||||
@@ -441,7 +443,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
newType.addAll(ct.getAddType());
|
||||
}
|
||||
}
|
||||
return newType;
|
||||
return newType == null ? this : newType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package forge.card;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import forge.card.CardType.CoreType;
|
||||
@@ -39,5 +38,5 @@ public interface CardTypeView extends Iterable<String>, Serializable {
|
||||
boolean isPhenomenon();
|
||||
boolean isEmblem();
|
||||
boolean isTribal();
|
||||
CardTypeView getTypeWithChanges(Map<Long, CardChangedType> changedCardTypes);
|
||||
CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes);
|
||||
}
|
||||
|
||||
@@ -78,10 +78,6 @@ public class DeckGenerator5Color extends DeckGeneratorBase {
|
||||
colors = ColorSet.fromMask(0).inverse();
|
||||
}
|
||||
|
||||
private void initialize(DeckFormat format0) {
|
||||
format0.adjustCMCLevels(cmcLevels);
|
||||
colors = ColorSet.fromMask(0).inverse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final CardPool getDeck(final int size, final boolean forAi) {
|
||||
|
||||
@@ -36,7 +36,6 @@ import forge.util.MyRandom;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
import java.awt.print.Paper;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
@@ -23,8 +23,8 @@ import com.google.common.collect.ImmutableList;
|
||||
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
import forge.card.BoosterSlots;
|
||||
import forge.card.CardEdition;
|
||||
import forge.item.generation.BoosterSlots;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
@@ -28,8 +28,8 @@ import com.google.common.base.Function;
|
||||
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
import forge.card.BoosterGenerator;
|
||||
import forge.card.CardEdition;
|
||||
import forge.item.generation.BoosterGenerator;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.storage.StorageReaderFile;
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.StaticData;
|
||||
import forge.card.BoosterGenerator;
|
||||
import forge.card.BoosterSlots;
|
||||
import forge.card.CardRulesPredicates;
|
||||
import forge.item.generation.BoosterGenerator;
|
||||
import forge.item.generation.BoosterSlots;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.storage.StorageReaderFile;
|
||||
|
||||
@@ -21,8 +21,8 @@ import com.google.common.base.Function;
|
||||
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
import forge.card.BoosterGenerator;
|
||||
import forge.card.CardEdition;
|
||||
import forge.item.generation.BoosterGenerator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -1,487 +1,500 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.card;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardEdition.FoilType;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.item.IPaperCard.Predicates.Presets;
|
||||
import forge.item.PaperCard;
|
||||
import forge.item.SealedProduct;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* BoosterGenerator class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
public class BoosterGenerator {
|
||||
|
||||
|
||||
private final static Map<String, PrintSheet> cachedSheets = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private static synchronized PrintSheet getPrintSheet(String key) {
|
||||
if( !cachedSheets.containsKey(key) )
|
||||
cachedSheets.put(key, makeSheet(key, StaticData.instance().getCommonCards().getAllCards()));
|
||||
return cachedSheets.get(key);
|
||||
}
|
||||
|
||||
private static PaperCard generateFoilCard(PrintSheet sheet) {
|
||||
return StaticData.instance().getCommonCards().getFoiled(sheet.random(1, true).get(0));
|
||||
}
|
||||
|
||||
private static PaperCard generateFoilCard(List<PaperCard> cardList) {
|
||||
Collections.shuffle(cardList);
|
||||
return StaticData.instance().getCommonCards().getFoiled(cardList.get(0));
|
||||
}
|
||||
|
||||
public static List<PaperCard> getBoosterPack(SealedProduct.Template template) {
|
||||
// TODO: tweak the chances of generating Masterpieces to be more authentic
|
||||
// (currently merely added to the Rare/Mythic Rare print sheet via ExtraFoilSheetKey)
|
||||
|
||||
List<PaperCard> result = new ArrayList<>();
|
||||
List<PrintSheet> sheetsUsed = new ArrayList<>();
|
||||
|
||||
CardEdition edition = StaticData.instance().getEditions().get(template.getEdition());
|
||||
boolean hasFoil = edition != null && !template.getSlots().isEmpty() && MyRandom.getRandom().nextDouble() < edition.getFoilChanceInBooster() && edition.getFoilType() != FoilType.NOT_SUPPORTED;
|
||||
boolean foilAtEndOfPack = hasFoil && edition.getFoilAlwaysInCommonSlot();
|
||||
|
||||
Random rand = new Random();
|
||||
// Foil chances
|
||||
// 1 Rare or Mythic rare (distribution ratio same as nonfoils)
|
||||
// 2-3 Uncommons
|
||||
// 4-6 Commons or Basic Lands
|
||||
// 7 Time Shifted
|
||||
// 8 VMA Special
|
||||
// 9 DFC
|
||||
// if result not valid for pack, reroll
|
||||
// Other special types of foil slots, add here
|
||||
CardRarity foilCard = CardRarity.Unknown;
|
||||
while (foilCard == CardRarity.Unknown) {
|
||||
int randomNum = rand.nextInt(9) + 1;
|
||||
switch (randomNum) {
|
||||
case 1:
|
||||
// Rare or Mythic
|
||||
foilCard = CardRarity.Rare;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
// Uncommon
|
||||
foilCard = CardRarity.Uncommon;
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
// Common or Basic Land
|
||||
foilCard = CardRarity.Common;
|
||||
break;
|
||||
case 7:
|
||||
// Time Spiral
|
||||
if (edition != null) {
|
||||
if (edition.getName().equals("Time Spiral")) {
|
||||
foilCard = CardRarity.Special;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
if (edition != null) {
|
||||
if (edition.getName().equals("Vintage Masters")) {
|
||||
// 1 in 53 packs, with 7 possibilities for the slot itself in VMA
|
||||
// (1 RareMythic, 2 Uncommon, 3 Common, 1 Special)
|
||||
if (rand.nextInt(53) <= 7) {
|
||||
foilCard = CardRarity.Special;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
if (edition != null) {
|
||||
// every sixth foil - same as rares
|
||||
if (template.hasSlot("dfc")) {
|
||||
foilCard = CardRarity.Special;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Insert any additional special rarities/slot types for special
|
||||
// sets here, for 11 and up
|
||||
default:
|
||||
foilCard = CardRarity.Common;
|
||||
// otherwise, default is Common or Basic Land
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String extraFoilSheetKey = edition != null ? edition.getAdditionalSheetForFoils() : "";
|
||||
boolean replaceCommon = edition != null && !template.getSlots().isEmpty()
|
||||
&& MyRandom.getRandom().nextDouble() < edition.getChanceReplaceCommonWith();
|
||||
|
||||
// Default, if no matching slot type is found : equal chance for each slot
|
||||
// should not have effect unless new sets that do not match existing
|
||||
// rarities are added
|
||||
String foilSlot = Aggregates.random(template.getSlots()).getLeft().split("[ :!]")[0];
|
||||
|
||||
if (hasFoil) {
|
||||
switch (foilCard) {
|
||||
case Rare:
|
||||
// Take whichever rare slot the pack has.
|
||||
// Not the "MYTHIC" slot, no pack has that AFAIK
|
||||
// and chance was for rare/mythic.
|
||||
if (template.hasSlot(BoosterSlots.RARE_MYTHIC)) {
|
||||
foilSlot = BoosterSlots.RARE_MYTHIC;
|
||||
} else if (template.hasSlot(BoosterSlots.RARE)) {
|
||||
foilSlot = BoosterSlots.RARE;
|
||||
} else if (template.hasSlot(BoosterSlots.UNCOMMON_RARE)) {
|
||||
foilSlot = BoosterSlots.UNCOMMON_RARE;
|
||||
}
|
||||
break;
|
||||
case Uncommon:
|
||||
if (template.hasSlot(BoosterSlots.UNCOMMON)) {
|
||||
foilSlot = BoosterSlots.UNCOMMON;
|
||||
} else if (template.hasSlot(BoosterSlots.UNCOMMON_RARE)) {
|
||||
foilSlot = BoosterSlots.UNCOMMON_RARE;
|
||||
}
|
||||
break;
|
||||
case Common:
|
||||
foilSlot = BoosterSlots.COMMON;
|
||||
if (template.hasSlot(BoosterSlots.BASIC_LAND)) {
|
||||
// According to information I found, Basic Lands
|
||||
// are on the common foil sheet, each type appearing once.
|
||||
// Large Sets usually have 110 commons and 20 lands.
|
||||
if (rand.nextInt(130) <= 20) {
|
||||
foilSlot = BoosterSlots.BASIC_LAND;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Special:
|
||||
if (template.hasSlot(BoosterSlots.TIME_SHIFTED)) {
|
||||
foilSlot = BoosterSlots.TIME_SHIFTED;
|
||||
} else if (template.hasSlot(BoosterSlots.DUAL_FACED_CARD)) {
|
||||
foilSlot = BoosterSlots.DUAL_FACED_CARD;
|
||||
} else if (template.hasSlot(BoosterSlots.SPECIAL)) {
|
||||
foilSlot = BoosterSlots.SPECIAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<PaperCard> foilCardGeneratedAndHeld = new ArrayList<>();
|
||||
|
||||
for (Pair<String, Integer> slot : template.getSlots()) {
|
||||
String slotType = slot.getLeft(); // add expansion symbol here?
|
||||
int numCards = slot.getRight();
|
||||
|
||||
String[] sType = TextUtil.splitWithParenthesis(slotType, ' ');
|
||||
String setCode = sType.length == 1 && template.getEdition() != null ? template.getEdition() : null;
|
||||
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
|
||||
: slotType.trim();
|
||||
|
||||
slotType = slotType.split("[ :!]")[0]; // add expansion symbol here?
|
||||
|
||||
boolean foilInThisSlot = hasFoil && (slotType.equals(foilSlot));
|
||||
|
||||
if ((!foilAtEndOfPack && foilInThisSlot)
|
||||
|| (foilAtEndOfPack && hasFoil && slotType.startsWith(BoosterSlots.COMMON))) {
|
||||
numCards--;
|
||||
}
|
||||
|
||||
if (replaceCommon && slotType.startsWith(BoosterSlots.COMMON)) {
|
||||
numCards--;
|
||||
String replaceKey = StaticData.instance().getEditions().contains(setCode)
|
||||
? edition.getSlotReplaceCommonWith().trim() + " " + setCode
|
||||
: edition.getSlotReplaceCommonWith().trim();
|
||||
PrintSheet replaceSheet = getPrintSheet(replaceKey);
|
||||
result.addAll(replaceSheet.random(1, true));
|
||||
sheetsUsed.add(replaceSheet);
|
||||
replaceCommon = false;
|
||||
}
|
||||
|
||||
PrintSheet ps = getPrintSheet(sheetKey);
|
||||
result.addAll(ps.random(numCards, true));
|
||||
sheetsUsed.add(ps);
|
||||
|
||||
if (foilInThisSlot) {
|
||||
if (!foilAtEndOfPack) {
|
||||
hasFoil = false;
|
||||
if (!extraFoilSheetKey.isEmpty()) {
|
||||
// TODO: extra foil sheets are currently reliably supported
|
||||
// only for boosters with FoilAlwaysInCommonSlot=True.
|
||||
// If FoilAlwaysInCommonSlot is false, a card from the extra
|
||||
// sheet may still replace a card in any slot.
|
||||
List<PaperCard> foilCards = new ArrayList<>();
|
||||
for (PaperCard card : ps.toFlatList()) {
|
||||
if (!foilCards.contains(card)) {
|
||||
foilCards.add(card);
|
||||
}
|
||||
}
|
||||
addCardsFromExtraSheet(foilCards, extraFoilSheetKey);
|
||||
result.add(generateFoilCard(foilCards));
|
||||
} else {
|
||||
result.add(generateFoilCard(ps));
|
||||
}
|
||||
} else { // foilAtEndOfPack
|
||||
if (!extraFoilSheetKey.isEmpty()) {
|
||||
// TODO: extra foil sheets are currently reliably supported
|
||||
// only for boosters with FoilAlwaysInCommonSlot=True.
|
||||
// If FoilAlwaysInCommonSlot is false, a card from the extra
|
||||
// sheet may still replace a card in any slot.
|
||||
List<PaperCard> foilCards = new ArrayList<>();
|
||||
for (PaperCard card : ps.toFlatList()) {
|
||||
if (!foilCards.contains(card)) {
|
||||
foilCards.add(card);
|
||||
}
|
||||
}
|
||||
addCardsFromExtraSheet(foilCards, extraFoilSheetKey);
|
||||
foilCardGeneratedAndHeld.add(generateFoilCard(foilCards));
|
||||
} else {
|
||||
if (edition != null) {
|
||||
if (edition.getName().equals("Vintage Masters")) {
|
||||
// Vintage Masters foil slot
|
||||
// If "Special" was picked here, either foil or
|
||||
// nonfoil P9 needs to be generated
|
||||
// 1 out of ~30 normal and mythic rares are foil,
|
||||
// match that.
|
||||
// If not special card, make it always foil.
|
||||
if ((rand.nextInt(30) == 1) || (foilSlot != BoosterSlots.SPECIAL)) {
|
||||
foilCardGeneratedAndHeld.add(generateFoilCard(ps));
|
||||
} else {
|
||||
// Otherwise it's not foil (even though this is the
|
||||
// foil slot!)
|
||||
result.addAll(ps.random(1, true));
|
||||
}
|
||||
} else {
|
||||
foilCardGeneratedAndHeld.add(generateFoilCard(ps));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFoil && foilAtEndOfPack) {
|
||||
result.addAll(foilCardGeneratedAndHeld);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void addCardsFromExtraSheet(List<PaperCard> dest, String printSheetKey) {
|
||||
PrintSheet extraSheet = getPrintSheet(printSheetKey);
|
||||
|
||||
// try to determine the allowed rarity of the cards in dest
|
||||
Set<CardRarity> allowedRarity = Sets.newHashSet();
|
||||
if (!dest.isEmpty()) {
|
||||
for (PaperCard inDest : dest) {
|
||||
allowedRarity.add(inDest.getRarity());
|
||||
}
|
||||
}
|
||||
|
||||
for (PaperCard card : extraSheet.toFlatList()) {
|
||||
if (!dest.contains(card) && allowedRarity.contains(card.getRarity())) {
|
||||
dest.add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static PrintSheet makeSheet(String sheetKey, Iterable<PaperCard> src) {
|
||||
|
||||
PrintSheet ps = new PrintSheet(sheetKey);
|
||||
String[] sKey = TextUtil.splitWithParenthesis(sheetKey, ' ', 2);
|
||||
Predicate<PaperCard> setPred = (Predicate<PaperCard>) (sKey.length > 1 ? IPaperCard.Predicates.printedInSets(sKey[1].split(" ")) : Predicates.alwaysTrue());
|
||||
|
||||
List<String> operators = new LinkedList<>(Arrays.asList(TextUtil.splitWithParenthesis(sKey[0], ':')));
|
||||
Predicate<PaperCard> extraPred = buildExtraPredicate(operators);
|
||||
|
||||
// source replacement operators - if one is applied setPredicate will be ignored
|
||||
Iterator<String> itMod = operators.iterator();
|
||||
while(itMod.hasNext()) {
|
||||
|
||||
String mainCode = itMod.next();
|
||||
|
||||
if (mainCode.regionMatches(true, 0, "fromSheet", 0, 9)) { // custom print sheet
|
||||
|
||||
String sheetName = StringUtils.strip(mainCode.substring(9), "()\" ");
|
||||
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||
setPred = Predicates.alwaysTrue();
|
||||
|
||||
} else if (mainCode.startsWith("promo")) { // get exactly the named cards, that's a tiny inlined print sheet
|
||||
|
||||
String list = StringUtils.strip(mainCode.substring(5), "() ");
|
||||
String[] cardNames = TextUtil.splitWithParenthesis(list, ',', '"', '"');
|
||||
List<PaperCard> srcList = new ArrayList<>();
|
||||
|
||||
for(String cardName: cardNames) {
|
||||
srcList.add(StaticData.instance().getCommonCards().getCard(cardName));
|
||||
}
|
||||
|
||||
src = srcList;
|
||||
setPred = Predicates.alwaysTrue();
|
||||
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
itMod.remove();
|
||||
|
||||
}
|
||||
|
||||
// only special operators should remain by now - the ones that could not be turned into one predicate
|
||||
String mainCode = operators.isEmpty() ? null : operators.get(0).trim();
|
||||
|
||||
if( null == mainCode || mainCode.equalsIgnoreCase(BoosterSlots.ANY) ) { // no restriction on rarity
|
||||
|
||||
Predicate<PaperCard> predicate = Predicates.and(setPred, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicate));
|
||||
|
||||
} else if ( mainCode.equalsIgnoreCase(BoosterSlots.UNCOMMON_RARE) ) { // for sets like ARN, where U1 cards are considered rare and U3 are uncommon
|
||||
|
||||
Predicate<PaperCard> predicateRares = Predicates.and(setPred, IPaperCard.Predicates.Presets.IS_RARE, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicateRares));
|
||||
|
||||
Predicate<PaperCard> predicateUncommon = Predicates.and( setPred, IPaperCard.Predicates.Presets.IS_UNCOMMON, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicateUncommon), 3);
|
||||
|
||||
} else if ( mainCode.equalsIgnoreCase(BoosterSlots.RARE_MYTHIC) ) {
|
||||
// Typical ratio of rares to mythics is 53:15, changing to 35:10 in smaller sets.
|
||||
// To achieve the desired 1:8 are all mythics are added once, and all rares added twice per print sheet.
|
||||
|
||||
Predicate<PaperCard> predicateMythic = Predicates.and( setPred, IPaperCard.Predicates.Presets.IS_MYTHIC_RARE, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicateMythic));
|
||||
|
||||
Predicate<PaperCard> predicateRare = Predicates.and( setPred, IPaperCard.Predicates.Presets.IS_RARE, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicateRare), 2);
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Booster generator: operator could not be parsed - " + mainCode);
|
||||
}
|
||||
|
||||
return ps;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method also modifies passed parameter
|
||||
*/
|
||||
private static Predicate<PaperCard> buildExtraPredicate(List<String> operators) {
|
||||
|
||||
List<Predicate<PaperCard>> conditions = new ArrayList<>();
|
||||
|
||||
Iterator<String> itOp = operators.iterator();
|
||||
while(itOp.hasNext()) {
|
||||
|
||||
String operator = itOp.next();
|
||||
if(StringUtils.isEmpty(operator)) {
|
||||
itOp.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
if(operator.endsWith("s")) {
|
||||
operator = operator.substring(0, operator.length() - 1);
|
||||
}
|
||||
|
||||
boolean invert = operator.charAt(0) == '!';
|
||||
if (invert) { operator = operator.substring(1); }
|
||||
|
||||
Predicate<PaperCard> toAdd = null;
|
||||
if (operator.equalsIgnoreCase(BoosterSlots.DUAL_FACED_CARD)) {
|
||||
toAdd = Predicates.compose(Predicates.or(CardRulesPredicates.splitType(CardSplitType.Transform), CardRulesPredicates.splitType(CardSplitType.Meld)),
|
||||
PaperCard.FN_GET_RULES);
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.LAND)) { toAdd = Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES);
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.BASIC_LAND)) { toAdd = IPaperCard.Predicates.Presets.IS_BASIC_LAND;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.TIME_SHIFTED)) { toAdd = IPaperCard.Predicates.Presets.IS_SPECIAL;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.SPECIAL)) { toAdd = IPaperCard.Predicates.Presets.IS_SPECIAL;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.MYTHIC)) { toAdd = IPaperCard.Predicates.Presets.IS_MYTHIC_RARE;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.RARE)) { toAdd = IPaperCard.Predicates.Presets.IS_RARE;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.UNCOMMON)) { toAdd = IPaperCard.Predicates.Presets.IS_UNCOMMON;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.COMMON)) { toAdd = IPaperCard.Predicates.Presets.IS_COMMON;
|
||||
} else if (operator.startsWith("name(")) {
|
||||
operator = StringUtils.strip(operator.substring(4), "() ");
|
||||
String[] cardNames = TextUtil.splitWithParenthesis(operator, ',', '"', '"');
|
||||
toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
|
||||
} else if (operator.startsWith("color(")) {
|
||||
operator = StringUtils.strip(operator.substring("color(".length() + 1), "()\" ");
|
||||
switch (operator.toLowerCase()) {
|
||||
case "black":
|
||||
toAdd = Presets.IS_BLACK;
|
||||
break;
|
||||
case "blue":
|
||||
toAdd = Presets.IS_BLUE;
|
||||
break;
|
||||
case "green":
|
||||
toAdd = Presets.IS_GREEN;
|
||||
break;
|
||||
case "red":
|
||||
toAdd = Presets.IS_RED;
|
||||
break;
|
||||
case "white":
|
||||
toAdd = Presets.IS_WHITE;
|
||||
break;
|
||||
case "colorless":
|
||||
toAdd = Presets.IS_COLORLESS;
|
||||
break;
|
||||
}
|
||||
} else if (operator.startsWith("fromSets(")) {
|
||||
operator = StringUtils.strip(operator.substring("fromSets(".length() + 1), "()\" ");
|
||||
String[] sets = operator.split(",");
|
||||
toAdd = IPaperCard.Predicates.printedInSets(sets);
|
||||
} else if (operator.startsWith("fromSheet(") && invert) {
|
||||
String sheetName = StringUtils.strip(operator.substring(9), "()\" ");
|
||||
Iterable<PaperCard> src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||
List<String> cardNames = Lists.newArrayList();
|
||||
for (PaperCard card : src) {
|
||||
cardNames.add(card.getName());
|
||||
}
|
||||
toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
|
||||
}
|
||||
|
||||
if (toAdd == null) {
|
||||
continue;
|
||||
} else {
|
||||
itOp.remove();
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
toAdd = Predicates.not(toAdd);
|
||||
}
|
||||
conditions.add(toAdd);
|
||||
|
||||
}
|
||||
|
||||
if (conditions.isEmpty()) {
|
||||
return Predicates.alwaysTrue();
|
||||
}
|
||||
|
||||
return Predicates.and(conditions);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.item.generation;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.CardRarity;
|
||||
import forge.card.CardRulesPredicates;
|
||||
import forge.card.CardSplitType;
|
||||
import forge.card.PrintSheet;
|
||||
import forge.card.CardEdition.FoilType;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.item.IPaperCard.Predicates.Presets;
|
||||
import forge.item.PaperCard;
|
||||
import forge.item.SealedProduct;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* BoosterGenerator class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: BoosterGenerator.java 35014 2017-08-13 00:40:48Z Max mtg $
|
||||
*/
|
||||
public class BoosterGenerator {
|
||||
|
||||
|
||||
private final static Map<String, PrintSheet> cachedSheets = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private static synchronized PrintSheet getPrintSheet(String key) {
|
||||
if( !cachedSheets.containsKey(key) )
|
||||
cachedSheets.put(key, makeSheet(key, StaticData.instance().getCommonCards().getAllCards()));
|
||||
return cachedSheets.get(key);
|
||||
}
|
||||
|
||||
private static PaperCard generateFoilCard(PrintSheet sheet) {
|
||||
return StaticData.instance().getCommonCards().getFoiled(sheet.random(1, true).get(0));
|
||||
}
|
||||
|
||||
private static PaperCard generateFoilCard(List<PaperCard> cardList) {
|
||||
Collections.shuffle(cardList);
|
||||
return StaticData.instance().getCommonCards().getFoiled(cardList.get(0));
|
||||
}
|
||||
|
||||
public static List<PaperCard> getBoosterPack(SealedProduct.Template template) {
|
||||
// TODO: tweak the chances of generating Masterpieces to be more authentic
|
||||
// (currently merely added to the Rare/Mythic Rare print sheet via ExtraFoilSheetKey)
|
||||
|
||||
List<PaperCard> result = new ArrayList<>();
|
||||
List<PrintSheet> sheetsUsed = new ArrayList<>();
|
||||
|
||||
CardEdition edition = StaticData.instance().getEditions().get(template.getEdition());
|
||||
|
||||
boolean hasFoil = edition != null
|
||||
&& !template.getSlots().isEmpty()
|
||||
&& MyRandom.getRandom().nextDouble() < edition.getFoilChanceInBooster()
|
||||
&& edition.getFoilType() != FoilType.NOT_SUPPORTED;
|
||||
boolean foilAtEndOfPack = hasFoil && edition.getFoilAlwaysInCommonSlot();
|
||||
|
||||
Random rand = new Random();
|
||||
// Foil chances
|
||||
// 1 Rare or Mythic rare (distribution ratio same as nonfoils)
|
||||
// 2-3 Uncommons
|
||||
// 4-6 Commons or Basic Lands
|
||||
// 7 Time Shifted
|
||||
// 8 VMA Special
|
||||
// 9 DFC
|
||||
// if result not valid for pack, reroll
|
||||
// Other special types of foil slots, add here
|
||||
CardRarity foilCard = CardRarity.Unknown;
|
||||
while (foilCard == CardRarity.Unknown) {
|
||||
int randomNum = rand.nextInt(9) + 1;
|
||||
switch (randomNum) {
|
||||
case 1:
|
||||
// Rare or Mythic
|
||||
foilCard = CardRarity.Rare;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
// Uncommon
|
||||
foilCard = CardRarity.Uncommon;
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
// Common or Basic Land
|
||||
foilCard = CardRarity.Common;
|
||||
break;
|
||||
case 7:
|
||||
// Time Spiral
|
||||
if (edition != null) {
|
||||
if (edition.getName().equals("Time Spiral")) {
|
||||
foilCard = CardRarity.Special;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
if (edition != null) {
|
||||
if (edition.getName().equals("Vintage Masters")) {
|
||||
// 1 in 53 packs, with 7 possibilities for the slot itself in VMA
|
||||
// (1 RareMythic, 2 Uncommon, 3 Common, 1 Special)
|
||||
if (rand.nextInt(53) <= 7) {
|
||||
foilCard = CardRarity.Special;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
if (edition != null) {
|
||||
// every sixth foil - same as rares
|
||||
if (template.hasSlot("dfc")) {
|
||||
foilCard = CardRarity.Special;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Insert any additional special rarities/slot types for special
|
||||
// sets here, for 11 and up
|
||||
default:
|
||||
foilCard = CardRarity.Common;
|
||||
// otherwise, default is Common or Basic Land
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String extraFoilSheetKey = edition != null ? edition.getAdditionalSheetForFoils() : "";
|
||||
boolean replaceCommon = edition != null && !template.getSlots().isEmpty()
|
||||
&& MyRandom.getRandom().nextDouble() < edition.getChanceReplaceCommonWith();
|
||||
|
||||
String foilSlot = "";
|
||||
|
||||
if (hasFoil) {
|
||||
// Default, if no matching slot type is found : equal chance for each slot
|
||||
// should not have effect unless new sets that do not match existing
|
||||
// rarities are added
|
||||
foilSlot = Aggregates.random(template.getSlots()).getLeft().split("[ :!]")[0];
|
||||
|
||||
switch (foilCard) {
|
||||
case Rare:
|
||||
// Take whichever rare slot the pack has.
|
||||
// Not the "MYTHIC" slot, no pack has that AFAIK
|
||||
// and chance was for rare/mythic.
|
||||
if (template.hasSlot(BoosterSlots.RARE_MYTHIC)) {
|
||||
foilSlot = BoosterSlots.RARE_MYTHIC;
|
||||
} else if (template.hasSlot(BoosterSlots.RARE)) {
|
||||
foilSlot = BoosterSlots.RARE;
|
||||
} else if (template.hasSlot(BoosterSlots.UNCOMMON_RARE)) {
|
||||
foilSlot = BoosterSlots.UNCOMMON_RARE;
|
||||
}
|
||||
break;
|
||||
case Uncommon:
|
||||
if (template.hasSlot(BoosterSlots.UNCOMMON)) {
|
||||
foilSlot = BoosterSlots.UNCOMMON;
|
||||
} else if (template.hasSlot(BoosterSlots.UNCOMMON_RARE)) {
|
||||
foilSlot = BoosterSlots.UNCOMMON_RARE;
|
||||
}
|
||||
break;
|
||||
case Common:
|
||||
foilSlot = BoosterSlots.COMMON;
|
||||
if (template.hasSlot(BoosterSlots.BASIC_LAND)) {
|
||||
// According to information I found, Basic Lands
|
||||
// are on the common foil sheet, each type appearing once.
|
||||
// Large Sets usually have 110 commons and 20 lands.
|
||||
if (rand.nextInt(130) <= 20) {
|
||||
foilSlot = BoosterSlots.BASIC_LAND;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Special:
|
||||
if (template.hasSlot(BoosterSlots.TIME_SHIFTED)) {
|
||||
foilSlot = BoosterSlots.TIME_SHIFTED;
|
||||
} else if (template.hasSlot(BoosterSlots.DUAL_FACED_CARD)) {
|
||||
foilSlot = BoosterSlots.DUAL_FACED_CARD;
|
||||
} else if (template.hasSlot(BoosterSlots.SPECIAL)) {
|
||||
foilSlot = BoosterSlots.SPECIAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<PaperCard> foilCardGeneratedAndHeld = new ArrayList<>();
|
||||
|
||||
for (Pair<String, Integer> slot : template.getSlots()) {
|
||||
String slotType = slot.getLeft(); // add expansion symbol here?
|
||||
int numCards = slot.getRight();
|
||||
|
||||
String[] sType = TextUtil.splitWithParenthesis(slotType, ' ');
|
||||
String setCode = sType.length == 1 && template.getEdition() != null ? template.getEdition() : null;
|
||||
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
|
||||
: slotType.trim();
|
||||
|
||||
slotType = slotType.split("[ :!]")[0]; // add expansion symbol here?
|
||||
|
||||
boolean foilInThisSlot = hasFoil && (slotType.equals(foilSlot));
|
||||
|
||||
if ((!foilAtEndOfPack && foilInThisSlot)
|
||||
|| (foilAtEndOfPack && hasFoil && slotType.startsWith(BoosterSlots.COMMON))) {
|
||||
numCards--;
|
||||
}
|
||||
|
||||
if (replaceCommon && slotType.startsWith(BoosterSlots.COMMON)) {
|
||||
numCards--;
|
||||
String replaceKey = StaticData.instance().getEditions().contains(setCode)
|
||||
? edition.getSlotReplaceCommonWith().trim() + " " + setCode
|
||||
: edition.getSlotReplaceCommonWith().trim();
|
||||
PrintSheet replaceSheet = getPrintSheet(replaceKey);
|
||||
result.addAll(replaceSheet.random(1, true));
|
||||
sheetsUsed.add(replaceSheet);
|
||||
replaceCommon = false;
|
||||
}
|
||||
|
||||
PrintSheet ps = getPrintSheet(sheetKey);
|
||||
result.addAll(ps.random(numCards, true));
|
||||
sheetsUsed.add(ps);
|
||||
|
||||
if (foilInThisSlot) {
|
||||
if (!foilAtEndOfPack) {
|
||||
hasFoil = false;
|
||||
if (!extraFoilSheetKey.isEmpty()) {
|
||||
// TODO: extra foil sheets are currently reliably supported
|
||||
// only for boosters with FoilAlwaysInCommonSlot=True.
|
||||
// If FoilAlwaysInCommonSlot is false, a card from the extra
|
||||
// sheet may still replace a card in any slot.
|
||||
List<PaperCard> foilCards = new ArrayList<>();
|
||||
for (PaperCard card : ps.toFlatList()) {
|
||||
if (!foilCards.contains(card)) {
|
||||
foilCards.add(card);
|
||||
}
|
||||
}
|
||||
addCardsFromExtraSheet(foilCards, extraFoilSheetKey);
|
||||
result.add(generateFoilCard(foilCards));
|
||||
} else {
|
||||
result.add(generateFoilCard(ps));
|
||||
}
|
||||
} else { // foilAtEndOfPack
|
||||
if (!extraFoilSheetKey.isEmpty()) {
|
||||
// TODO: extra foil sheets are currently reliably supported
|
||||
// only for boosters with FoilAlwaysInCommonSlot=True.
|
||||
// If FoilAlwaysInCommonSlot is false, a card from the extra
|
||||
// sheet may still replace a card in any slot.
|
||||
List<PaperCard> foilCards = new ArrayList<>();
|
||||
for (PaperCard card : ps.toFlatList()) {
|
||||
if (!foilCards.contains(card)) {
|
||||
foilCards.add(card);
|
||||
}
|
||||
}
|
||||
addCardsFromExtraSheet(foilCards, extraFoilSheetKey);
|
||||
foilCardGeneratedAndHeld.add(generateFoilCard(foilCards));
|
||||
} else {
|
||||
if (edition != null) {
|
||||
if (edition.getName().equals("Vintage Masters")) {
|
||||
// Vintage Masters foil slot
|
||||
// If "Special" was picked here, either foil or
|
||||
// nonfoil P9 needs to be generated
|
||||
// 1 out of ~30 normal and mythic rares are foil,
|
||||
// match that.
|
||||
// If not special card, make it always foil.
|
||||
if ((rand.nextInt(30) == 1) || (foilSlot != BoosterSlots.SPECIAL)) {
|
||||
foilCardGeneratedAndHeld.add(generateFoilCard(ps));
|
||||
} else {
|
||||
// Otherwise it's not foil (even though this is the
|
||||
// foil slot!)
|
||||
result.addAll(ps.random(1, true));
|
||||
}
|
||||
} else {
|
||||
foilCardGeneratedAndHeld.add(generateFoilCard(ps));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFoil && foilAtEndOfPack) {
|
||||
result.addAll(foilCardGeneratedAndHeld);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void addCardsFromExtraSheet(List<PaperCard> dest, String printSheetKey) {
|
||||
PrintSheet extraSheet = getPrintSheet(printSheetKey);
|
||||
|
||||
// try to determine the allowed rarity of the cards in dest
|
||||
Set<CardRarity> allowedRarity = Sets.newHashSet();
|
||||
if (!dest.isEmpty()) {
|
||||
for (PaperCard inDest : dest) {
|
||||
allowedRarity.add(inDest.getRarity());
|
||||
}
|
||||
}
|
||||
|
||||
for (PaperCard card : extraSheet.toFlatList()) {
|
||||
if (!dest.contains(card) && allowedRarity.contains(card.getRarity())) {
|
||||
dest.add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static PrintSheet makeSheet(String sheetKey, Iterable<PaperCard> src) {
|
||||
|
||||
PrintSheet ps = new PrintSheet(sheetKey);
|
||||
String[] sKey = TextUtil.splitWithParenthesis(sheetKey, ' ', 2);
|
||||
Predicate<PaperCard> setPred = (Predicate<PaperCard>) (sKey.length > 1 ? IPaperCard.Predicates.printedInSets(sKey[1].split(" ")) : Predicates.alwaysTrue());
|
||||
|
||||
List<String> operators = new LinkedList<>(Arrays.asList(TextUtil.splitWithParenthesis(sKey[0], ':')));
|
||||
Predicate<PaperCard> extraPred = buildExtraPredicate(operators);
|
||||
|
||||
// source replacement operators - if one is applied setPredicate will be ignored
|
||||
Iterator<String> itMod = operators.iterator();
|
||||
while(itMod.hasNext()) {
|
||||
|
||||
String mainCode = itMod.next();
|
||||
|
||||
if (mainCode.regionMatches(true, 0, "fromSheet", 0, 9)) { // custom print sheet
|
||||
|
||||
String sheetName = StringUtils.strip(mainCode.substring(9), "()\" ");
|
||||
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||
setPred = Predicates.alwaysTrue();
|
||||
|
||||
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet
|
||||
|
||||
String list = StringUtils.strip(mainCode.substring(5), "() ");
|
||||
String[] cardNames = TextUtil.splitWithParenthesis(list, ',', '"', '"');
|
||||
List<PaperCard> srcList = new ArrayList<>();
|
||||
|
||||
for(String cardName: cardNames) {
|
||||
srcList.add(StaticData.instance().getCommonCards().getCard(cardName));
|
||||
}
|
||||
|
||||
src = srcList;
|
||||
setPred = Predicates.alwaysTrue();
|
||||
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
itMod.remove();
|
||||
|
||||
}
|
||||
|
||||
// only special operators should remain by now - the ones that could not be turned into one predicate
|
||||
String mainCode = operators.isEmpty() ? null : operators.get(0).trim();
|
||||
|
||||
if( null == mainCode || mainCode.equalsIgnoreCase(BoosterSlots.ANY) ) { // no restriction on rarity
|
||||
|
||||
Predicate<PaperCard> predicate = Predicates.and(setPred, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicate));
|
||||
|
||||
} else if ( mainCode.equalsIgnoreCase(BoosterSlots.UNCOMMON_RARE) ) { // for sets like ARN, where U1 cards are considered rare and U3 are uncommon
|
||||
|
||||
Predicate<PaperCard> predicateRares = Predicates.and(setPred, IPaperCard.Predicates.Presets.IS_RARE, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicateRares));
|
||||
|
||||
Predicate<PaperCard> predicateUncommon = Predicates.and( setPred, IPaperCard.Predicates.Presets.IS_UNCOMMON, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicateUncommon), 3);
|
||||
|
||||
} else if ( mainCode.equalsIgnoreCase(BoosterSlots.RARE_MYTHIC) ) {
|
||||
// Typical ratio of rares to mythics is 53:15, changing to 35:10 in smaller sets.
|
||||
// To achieve the desired 1:8 are all mythics are added once, and all rares added twice per print sheet.
|
||||
|
||||
Predicate<PaperCard> predicateMythic = Predicates.and( setPred, IPaperCard.Predicates.Presets.IS_MYTHIC_RARE, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicateMythic));
|
||||
|
||||
Predicate<PaperCard> predicateRare = Predicates.and( setPred, IPaperCard.Predicates.Presets.IS_RARE, extraPred);
|
||||
ps.addAll(Iterables.filter(src, predicateRare), 2);
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Booster generator: operator could not be parsed - " + mainCode);
|
||||
}
|
||||
|
||||
return ps;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method also modifies passed parameter
|
||||
*/
|
||||
private static Predicate<PaperCard> buildExtraPredicate(List<String> operators) {
|
||||
|
||||
List<Predicate<PaperCard>> conditions = new ArrayList<>();
|
||||
|
||||
Iterator<String> itOp = operators.iterator();
|
||||
while(itOp.hasNext()) {
|
||||
|
||||
String operator = itOp.next();
|
||||
if(StringUtils.isEmpty(operator)) {
|
||||
itOp.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
if(operator.endsWith("s")) {
|
||||
operator = operator.substring(0, operator.length() - 1);
|
||||
}
|
||||
|
||||
boolean invert = operator.charAt(0) == '!';
|
||||
if (invert) { operator = operator.substring(1); }
|
||||
|
||||
Predicate<PaperCard> toAdd = null;
|
||||
if (operator.equalsIgnoreCase(BoosterSlots.DUAL_FACED_CARD)) {
|
||||
toAdd = Predicates.compose(Predicates.or(CardRulesPredicates.splitType(CardSplitType.Transform), CardRulesPredicates.splitType(CardSplitType.Meld)),
|
||||
PaperCard.FN_GET_RULES);
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.LAND)) { toAdd = Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES);
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.BASIC_LAND)) { toAdd = IPaperCard.Predicates.Presets.IS_BASIC_LAND;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.TIME_SHIFTED)) { toAdd = IPaperCard.Predicates.Presets.IS_SPECIAL;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.SPECIAL)) { toAdd = IPaperCard.Predicates.Presets.IS_SPECIAL;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.MYTHIC)) { toAdd = IPaperCard.Predicates.Presets.IS_MYTHIC_RARE;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.RARE)) { toAdd = IPaperCard.Predicates.Presets.IS_RARE;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.UNCOMMON)) { toAdd = IPaperCard.Predicates.Presets.IS_UNCOMMON;
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.COMMON)) { toAdd = IPaperCard.Predicates.Presets.IS_COMMON;
|
||||
} else if (operator.startsWith("name(")) {
|
||||
operator = StringUtils.strip(operator.substring(4), "() ");
|
||||
String[] cardNames = TextUtil.splitWithParenthesis(operator, ',', '"', '"');
|
||||
toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
|
||||
} else if (operator.startsWith("color(")) {
|
||||
operator = StringUtils.strip(operator.substring("color(".length() + 1), "()\" ");
|
||||
switch (operator.toLowerCase()) {
|
||||
case "black":
|
||||
toAdd = Presets.IS_BLACK;
|
||||
break;
|
||||
case "blue":
|
||||
toAdd = Presets.IS_BLUE;
|
||||
break;
|
||||
case "green":
|
||||
toAdd = Presets.IS_GREEN;
|
||||
break;
|
||||
case "red":
|
||||
toAdd = Presets.IS_RED;
|
||||
break;
|
||||
case "white":
|
||||
toAdd = Presets.IS_WHITE;
|
||||
break;
|
||||
case "colorless":
|
||||
toAdd = Presets.IS_COLORLESS;
|
||||
break;
|
||||
}
|
||||
} else if (operator.startsWith("fromSets(")) {
|
||||
operator = StringUtils.strip(operator.substring("fromSets(".length() + 1), "()\" ");
|
||||
String[] sets = operator.split(",");
|
||||
toAdd = IPaperCard.Predicates.printedInSets(sets);
|
||||
} else if (operator.startsWith("fromSheet(") && invert) {
|
||||
String sheetName = StringUtils.strip(operator.substring(9), "()\" ");
|
||||
Iterable<PaperCard> src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||
List<String> cardNames = Lists.newArrayList();
|
||||
for (PaperCard card : src) {
|
||||
cardNames.add(card.getName());
|
||||
}
|
||||
toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
|
||||
}
|
||||
|
||||
if (toAdd == null) {
|
||||
continue;
|
||||
} else {
|
||||
itOp.remove();
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
toAdd = Predicates.not(toAdd);
|
||||
}
|
||||
conditions.add(toAdd);
|
||||
|
||||
}
|
||||
|
||||
if (conditions.isEmpty()) {
|
||||
return Predicates.alwaysTrue();
|
||||
}
|
||||
|
||||
return Predicates.and(conditions);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package forge.card;
|
||||
package forge.item.generation;
|
||||
|
||||
public class BoosterSlots {
|
||||
public static final String LAND = "Land";
|
||||
@@ -1,4 +1,4 @@
|
||||
package forge.card;
|
||||
package forge.item.generation;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import forge.item.PaperCard;
|
||||
@@ -1,8 +1,9 @@
|
||||
package forge.card;
|
||||
package forge.item.generation;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.StaticData;
|
||||
import forge.card.PrintSheet;
|
||||
import forge.item.PaperCard;
|
||||
import forge.item.SealedProduct;
|
||||
import forge.util.ItemPool;
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.1</version>
|
||||
<version>1.6.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
182
forge-game/src/main/java/forge/game/ForgeScript.java
Normal file
182
forge-game/src/main/java/forge/game/ForgeScript.java
Normal file
@@ -0,0 +1,182 @@
|
||||
package forge.game;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Expressions;
|
||||
|
||||
public class ForgeScript {
|
||||
|
||||
public static boolean cardStateHasProperty(CardState cardState, String property, Player sourceController,
|
||||
Card source, SpellAbility spellAbility) {
|
||||
|
||||
final boolean isColorlessSource = cardState.getCard().hasKeyword("Colorless Damage Source", cardState);
|
||||
final ColorSet colors = cardState.getCard().determineColor(cardState);
|
||||
if (property.contains("White") || property.contains("Blue") || property.contains("Black")
|
||||
|| property.contains("Red") || property.contains("Green")) {
|
||||
boolean mustHave = !property.startsWith("non");
|
||||
boolean withSource = property.endsWith("Source");
|
||||
if (withSource && isColorlessSource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String colorName = property.substring(mustHave ? 0 : 3, property.length() - (withSource ? 6 : 0));
|
||||
|
||||
int desiredColor = MagicColor.fromName(colorName);
|
||||
boolean hasColor = colors.hasAnyColor(desiredColor);
|
||||
if (mustHave != hasColor)
|
||||
return false;
|
||||
|
||||
} else if (property.contains("Colorless")) { // ... Card is colorless
|
||||
boolean non = property.startsWith("non");
|
||||
boolean withSource = property.endsWith("Source");
|
||||
if (non && withSource && isColorlessSource) {
|
||||
return false;
|
||||
}
|
||||
if (non == colors.isColorless()) return false;
|
||||
|
||||
} else if (property.contains("MultiColor")) {
|
||||
// ... Card is multicolored
|
||||
if (property.endsWith("Source") && isColorlessSource)
|
||||
return false;
|
||||
if (property.startsWith("non") == colors.isMulticolor())
|
||||
return false;
|
||||
|
||||
} else if (property.contains("MonoColor")) { // ... Card is monocolored
|
||||
if (property.endsWith("Source") && isColorlessSource)
|
||||
return false;
|
||||
if (property.startsWith("non") == colors.isMonoColor())
|
||||
return false;
|
||||
|
||||
} else if (property.startsWith("ChosenColor")) {
|
||||
if (property.endsWith("Source") && isColorlessSource)
|
||||
return false;
|
||||
if (!source.hasChosenColor() || !colors.hasAnyColor(MagicColor.fromName(source.getChosenColor())))
|
||||
return false;
|
||||
|
||||
} else if (property.startsWith("AnyChosenColor")) {
|
||||
if (property.endsWith("Source") && isColorlessSource)
|
||||
return false;
|
||||
if (!source.hasChosenColor()
|
||||
|| !colors.hasAnyColor(ColorSet.fromNames(source.getChosenColors()).getColor()))
|
||||
return false;
|
||||
|
||||
} else if (property.startsWith("non")) {
|
||||
// ... Other Card types
|
||||
if (cardState.getTypeWithChanges().hasStringType(property.substring(3))) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("CostsPhyrexianMana")) {
|
||||
if (!cardState.getManaCost().hasPhyrexian()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("HasSVar")) {
|
||||
final String svar = property.substring(8);
|
||||
if (!cardState.hasSVar(svar)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("ChosenType")) {
|
||||
if (!cardState.getTypeWithChanges().hasStringType(source.getChosenType())) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("IsNotChosenType")) {
|
||||
if (cardState.getTypeWithChanges().hasStringType(source.getChosenType())) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("HasSubtype")) {
|
||||
final String subType = property.substring(11);
|
||||
if (!cardState.getTypeWithChanges().hasSubtype(subType)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("HasNoSubtype")) {
|
||||
final String subType = property.substring(13);
|
||||
if (cardState.getTypeWithChanges().hasSubtype(subType)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("hasActivatedAbilityWithTapCost")) {
|
||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
||||
if (sa.isAbility() && (sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (property.equals("hasActivatedAbility")) {
|
||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
||||
if (sa.isAbility()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (property.equals("hasManaAbility")) {
|
||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
||||
if (sa.isManaAbility()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (property.equals("hasNonManaActivatedAbility")) {
|
||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
||||
if (sa.isAbility() && !sa.isManaAbility()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (property.startsWith("cmc")) {
|
||||
int x;
|
||||
String rhs = property.substring(5);
|
||||
int y = cardState.getManaCost().getCMC();
|
||||
try {
|
||||
x = Integer.parseInt(rhs);
|
||||
} catch (final NumberFormatException e) {
|
||||
x = AbilityUtils.calculateAmount(source, rhs, spellAbility);
|
||||
}
|
||||
|
||||
if (!Expressions.compare(y, property, x)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!cardState.getTypeWithChanges().hasStringType(property)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static boolean spellAbilityHasProperty(SpellAbility sa, String property, Player sourceController,
|
||||
Card source, SpellAbility spellAbility) {
|
||||
if (property.equals("Buyback")) {
|
||||
if (!sa.isBuyBackAbility()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("Cycling")) {
|
||||
if (!sa.isCycling()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("Dash")) {
|
||||
if (!sa.isDash()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("Flashback")) {
|
||||
if (!sa.isFlashBackAbility()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("MorphUp")) {
|
||||
if (!sa.isMorphUp()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("Equip")) {
|
||||
if (!sa.hasParam("Equip")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
|
||||
@@ -90,6 +91,9 @@ public class Game {
|
||||
private CardCollection lastStateBattlefield = new CardCollection();
|
||||
private CardCollection lastStateGraveyard = new CardCollection();
|
||||
|
||||
private Map<Player, PlayerCollection> attackedThisTurn = Maps.newHashMap();
|
||||
private Map<Player, PlayerCollection> attackedLastTurn = Maps.newHashMap();
|
||||
|
||||
private Player monarch = null;
|
||||
private Player monarchBeginTurn = null;
|
||||
|
||||
@@ -120,6 +124,28 @@ public class Game {
|
||||
this.monarchBeginTurn = monarchBeginTurn;
|
||||
}
|
||||
|
||||
public Map<Player, PlayerCollection> getPlayersAttackedThisTurn() {
|
||||
return attackedThisTurn;
|
||||
}
|
||||
|
||||
public Map<Player, PlayerCollection> getPlayersAttackedLastTurn() {
|
||||
return attackedLastTurn;
|
||||
}
|
||||
|
||||
public void addPlayerAttackedThisTurn(Player attacker, Player defender) {
|
||||
PlayerCollection atk = attackedThisTurn.get(attacker);
|
||||
if (atk == null) {
|
||||
attackedThisTurn.put(attacker, new PlayerCollection());
|
||||
}
|
||||
attackedThisTurn.get(attacker).add(defender);
|
||||
}
|
||||
|
||||
public void resetPlayersAttackedOnNextTurn() {
|
||||
attackedLastTurn.clear();
|
||||
attackedLastTurn.putAll(attackedThisTurn);
|
||||
attackedThisTurn.clear();
|
||||
}
|
||||
|
||||
public CardCollectionView getLastStateBattlefield() {
|
||||
return lastStateBattlefield;
|
||||
}
|
||||
@@ -626,8 +652,13 @@ public class Game {
|
||||
public void onPlayerLost(Player p) {
|
||||
// Rule 800.4 Losing a Multiplayer game
|
||||
CardCollectionView cards = this.getCardsInGame();
|
||||
boolean planarControllerLost = false;
|
||||
|
||||
for(Card c : cards) {
|
||||
if (c.getController().equals(p) && (c.isPlane() || c.isPhenomenon())) {
|
||||
planarControllerLost = true;
|
||||
}
|
||||
|
||||
if (c.getOwner().equals(p)) {
|
||||
c.ceaseToExist();
|
||||
} else {
|
||||
@@ -636,6 +667,17 @@ public class Game {
|
||||
this.getAction().exile(c, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 901.6: If the current planar controller would leave the game, instead the next player
|
||||
// in turn order that wouldn’t leave the game becomes the planar controller, then the old
|
||||
// planar controller leaves
|
||||
// 901.10: When a player leaves the game, all objects owned by that player except abilities
|
||||
// from phenomena leave the game. (See rule 800.4a.) If that includes a face-up plane card
|
||||
// or phenomenon card, the planar controller turns the top card of his or her planar deck face up.the game.
|
||||
if (planarControllerLost) {
|
||||
getNextPlayerAfter(p).initPlane();
|
||||
}
|
||||
|
||||
if (p != null && p.equals(getMonarch())) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package forge.game;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -257,6 +258,36 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
// special rule for Worms of the Earth
|
||||
if (toBattlefield && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLandBattlefield)) {
|
||||
// something that is already a Land cant enter the battlefield
|
||||
if (c.isLand()) {
|
||||
return c;
|
||||
}
|
||||
// check if something would be a land
|
||||
Card noLandLKI = CardUtil.getLKICopy(c);
|
||||
|
||||
//setZone is not enough
|
||||
//noLandLKI.setZone(zoneTo);
|
||||
noLandLKI.setImmutable(true); // immu doesn't trigger Zone
|
||||
zoneTo.add(noLandLKI);
|
||||
|
||||
noLandLKI.setImmutable(false); // but need to remove that or Static doesn't find it
|
||||
checkStaticAbilities(false, Sets.newHashSet(noLandLKI));
|
||||
|
||||
boolean noLand = noLandLKI.isLand();
|
||||
zoneTo.remove(noLandLKI);
|
||||
|
||||
// reset static
|
||||
checkStaticAbilities(false, Sets.newHashSet(noLandLKI));
|
||||
|
||||
if(noLand) {
|
||||
// if something would only be a land when entering the battlefield and not before
|
||||
// put it into the graveyard instead
|
||||
zoneTo = c.getOwner().getZone(ZoneType.Graveyard);
|
||||
}
|
||||
}
|
||||
|
||||
if (!suppress) {
|
||||
if (zoneFrom == null) {
|
||||
copied.getOwner().addInboundToken(copied);
|
||||
@@ -367,7 +398,7 @@ public class GameAction {
|
||||
runParams.put("Destination", zoneTo.getZoneType().name());
|
||||
runParams.put("SpellAbilityStackInstance", game.stack.peek());
|
||||
runParams.put("IndividualCostPaymentInstance", game.costPaymentStack.peek());
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, false);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, true);
|
||||
if (zoneFrom != null && zoneFrom.is(ZoneType.Battlefield)) {
|
||||
final Map<String, Object> runParams2 = Maps.newHashMap();
|
||||
runParams2.put("Card", lastKnownInfo);
|
||||
@@ -436,6 +467,8 @@ public class GameAction {
|
||||
copied.setState(CardStateName.Original, true);
|
||||
}
|
||||
unattachCardLeavingBattlefield(copied);
|
||||
// Remove all changed keywords
|
||||
copied.removeAllChangedText(game.getNextTimestamp());
|
||||
} else if (toBattlefield) {
|
||||
// reset timestamp in changezone effects so they have same timestamp if ETB simutaneously
|
||||
copied.setTimestamp(game.getNextTimestamp());
|
||||
@@ -715,11 +748,14 @@ public class GameAction {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO Java 1.8 use comparingLong
|
||||
|
||||
final Comparator<StaticAbility> comp = new Comparator<StaticAbility>() {
|
||||
@Override
|
||||
public int compare(final StaticAbility a, final StaticAbility b) {
|
||||
return Long.compare(a.getHostCard().getTimestamp(), b.getHostCard().getTimestamp());
|
||||
return ComparisonChain.start()
|
||||
.compareTrueFirst(a.hasParam("CharacteristicDefining"), b.hasParam("CharacteristicDefining"))
|
||||
.compare(a.getHostCard().getTimestamp(), b.getHostCard().getTimestamp())
|
||||
.result();
|
||||
}
|
||||
};
|
||||
Collections.sort(staticAbilities, comp);
|
||||
|
||||
@@ -285,6 +285,8 @@ public final class GameActionUtil {
|
||||
case Retrace:
|
||||
result.getRestrictions().setZone(ZoneType.Graveyard);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -32,9 +32,11 @@ public enum GlobalRuleChange {
|
||||
onlyOneAttackerATurn ("No more than one creature can attack each turn."),
|
||||
onlyOneAttackerACombat ("No more than one creature can attack each combat."),
|
||||
onlyOneBlocker ("No more than one creature can block each combat."),
|
||||
onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."),
|
||||
onlyTwoBlockers ("No more than two creatures can block each combat."),
|
||||
toughnessAssignsDamage ("Each creature assigns combat damage equal to its toughness rather than its power."),
|
||||
blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll.");
|
||||
blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll."),
|
||||
noLandBattlefield("Lands can't enter the battlefield.");
|
||||
|
||||
private final String ruleText;
|
||||
|
||||
|
||||
@@ -1,22 +1,6 @@
|
||||
package forge.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multiset;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import forge.LobbyPlayer;
|
||||
import forge.deck.CardPool;
|
||||
import forge.deck.Deck;
|
||||
@@ -31,8 +15,11 @@ import forge.game.trigger.Trigger;
|
||||
import forge.game.zone.PlayerZone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class Match {
|
||||
private final List<RegisteredPlayer> players;
|
||||
@@ -243,8 +230,6 @@ public class Match {
|
||||
}
|
||||
}
|
||||
|
||||
player.initVariantsZones(psc);
|
||||
|
||||
Deck myDeck = psc.getDeck();
|
||||
|
||||
Set<PaperCard> myRemovedAnteCards = null;
|
||||
@@ -263,8 +248,12 @@ public class Match {
|
||||
if (myDeck.has(DeckSection.Sideboard)) {
|
||||
preparePlayerLibrary(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil(), generator);
|
||||
}
|
||||
|
||||
player.initVariantsZones(psc);
|
||||
|
||||
player.shuffle(null);
|
||||
|
||||
|
||||
if (isFirstGame) {
|
||||
Collection<? extends PaperCard> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
|
||||
if (null != cardsComplained) {
|
||||
|
||||
@@ -253,7 +253,9 @@ public final class AbilityFactory {
|
||||
}
|
||||
|
||||
if (spellAbility instanceof SpellApiBased && hostCard.isPermanent()) {
|
||||
spellAbility.setDescription(spellAbility.getHostCard().getName());
|
||||
String desc = mapParams.containsKey("SpellDescription") ? mapParams.get("SpellDescription")
|
||||
: spellAbility.getHostCard().getName();
|
||||
spellAbility.setDescription(desc);
|
||||
} else if (mapParams.containsKey("SpellDescription")) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
|
||||
@@ -214,6 +214,11 @@ public class AbilityUtils {
|
||||
if (o != null && o instanceof Card) {
|
||||
cards.add(game.getCardState((Card) o));
|
||||
}
|
||||
} else if (defined.equals("LastRemembered")) {
|
||||
Object o = Iterables.getLast(hostCard.getRemembered(), null);
|
||||
if (o != null && o instanceof Card) {
|
||||
cards.add(game.getCardState((Card) o));
|
||||
}
|
||||
} else if (defined.equals("Clones")) {
|
||||
for (final Card clone : hostCard.getClones()) {
|
||||
cards.add(game.getCardState(clone));
|
||||
@@ -359,7 +364,7 @@ public class AbilityUtils {
|
||||
if (StringUtils.isNumeric(amount)) {
|
||||
int val = Integer.parseInt(amount);
|
||||
if (maxto) {
|
||||
val = Integer.max(val, 0);
|
||||
val = Math.max(val, 0);
|
||||
}
|
||||
return val * multiplier;
|
||||
}
|
||||
@@ -399,7 +404,7 @@ public class AbilityUtils {
|
||||
if (StringUtils.isNumeric(svarval)) {
|
||||
int val = Integer.parseInt(svarval);
|
||||
if (maxto) {
|
||||
val = Integer.max(val, 0);
|
||||
val = Math.max(val, 0);
|
||||
}
|
||||
return val * multiplier;
|
||||
}
|
||||
@@ -472,7 +477,7 @@ public class AbilityUtils {
|
||||
|
||||
if (val != null) {
|
||||
if (maxto) {
|
||||
val = Integer.max(val, 0);
|
||||
val = Math.max(val, 0);
|
||||
}
|
||||
return val * multiplier;
|
||||
}
|
||||
@@ -971,10 +976,14 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
else if (defined.startsWith("Triggered")) {
|
||||
String defParsed = defined.endsWith("AndYou") ? defined.substring(0, defined.indexOf("AndYou")) : defined;
|
||||
if (defined.endsWith("AndYou")) {
|
||||
players.add(sa.getActivatingPlayer());
|
||||
}
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
Object o = null;
|
||||
if (defined.endsWith("Controller")) {
|
||||
String triggeringType = defined.substring(9);
|
||||
if (defParsed.endsWith("Controller")) {
|
||||
String triggeringType = defParsed.substring(9);
|
||||
triggeringType = triggeringType.substring(0, triggeringType.length() - 10);
|
||||
final Object c = root.getTriggeringObject(triggeringType);
|
||||
if (c instanceof Card) {
|
||||
@@ -984,8 +993,8 @@ public class AbilityUtils {
|
||||
o = ((SpellAbility) c).getActivatingPlayer();
|
||||
}
|
||||
}
|
||||
else if (defined.endsWith("Opponent")) {
|
||||
String triggeringType = defined.substring(9);
|
||||
else if (defParsed.endsWith("Opponent")) {
|
||||
String triggeringType = defParsed.substring(9);
|
||||
triggeringType = triggeringType.substring(0, triggeringType.length() - 8);
|
||||
final Object c = root.getTriggeringObject(triggeringType);
|
||||
if (c instanceof Card) {
|
||||
@@ -995,8 +1004,8 @@ public class AbilityUtils {
|
||||
o = ((SpellAbility) c).getActivatingPlayer().getOpponents();
|
||||
}
|
||||
}
|
||||
else if (defined.endsWith("Owner")) {
|
||||
String triggeringType = defined.substring(9);
|
||||
else if (defParsed.endsWith("Owner")) {
|
||||
String triggeringType = defParsed.substring(9);
|
||||
triggeringType = triggeringType.substring(0, triggeringType.length() - 5);
|
||||
final Object c = root.getTriggeringObject(triggeringType);
|
||||
if (c instanceof Card) {
|
||||
@@ -1004,7 +1013,7 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
else {
|
||||
final String triggeringType = defined.substring(9);
|
||||
final String triggeringType = defParsed.substring(9);
|
||||
o = root.getTriggeringObject(triggeringType);
|
||||
}
|
||||
if (o != null) {
|
||||
@@ -1302,7 +1311,11 @@ public class AbilityUtils {
|
||||
|
||||
// Needed - Equip an untapped creature with Sword of the Paruns then cast Deadshot on it. Should deal 2 more damage.
|
||||
game.getAction().checkStaticAbilities(); // this will refresh continuous abilities for players and permanents.
|
||||
game.getTriggerHandler().resetActiveTriggers();
|
||||
if (sa.isReplacementAbility() && abSub.getApi() == ApiType.InternalEtbReplacement) {
|
||||
game.getTriggerHandler().resetActiveTriggers(false);
|
||||
} else {
|
||||
game.getTriggerHandler().resetActiveTriggers();
|
||||
}
|
||||
AbilityUtils.resolveApiAbility(abSub, game);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -8,6 +9,8 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class AbandonEffect extends SpellAbilityEffect {
|
||||
|
||||
|
||||
@@ -19,13 +22,27 @@ public class AbandonEffect extends SpellAbilityEffect {
|
||||
Card source = sa.getHostCard();
|
||||
Player controller = source.getController();
|
||||
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, "Would you like to abandon the scheme " + source + "?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Game game = controller.getGame();
|
||||
|
||||
if (sa.hasParam("RememberAbandoned")) {
|
||||
source.addRemembered(source);
|
||||
}
|
||||
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
controller.getZone(ZoneType.Command).remove(source);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
|
||||
controller.getZone(ZoneType.SchemeDeck).add(source);
|
||||
|
||||
// Run triggers
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Scheme", source);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Abandoned, runParams, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ import forge.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
public class AttachEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
|
||||
@@ -3,18 +3,14 @@ package forge.game.ability.effects;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Expressions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BranchEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player player = host.getController();
|
||||
|
||||
// TODO Reuse SpellAbilityCondition and areMet() here instead of repeating each
|
||||
|
||||
|
||||
@@ -79,11 +79,11 @@ public class ChangeTextEffect extends SpellAbilityEffect {
|
||||
validTypes.clear();
|
||||
final List<String> forbiddenTypes = sa.hasParam("ForbiddenNewTypes") ? Lists.newArrayList(sa.getParam("ForbiddenNewTypes").split(",")) : Lists.<String>newArrayList();
|
||||
forbiddenTypes.add(changedTypeWordOriginal);
|
||||
if (changedTypeWordsArray[0].startsWith("Choose")) {
|
||||
if (changedTypeWordsArray[0].equals("ChooseBasicLandType")) {
|
||||
if (changedTypeWordsArray[1].startsWith("Choose")) {
|
||||
if (changedTypeWordsArray[1].equals("ChooseBasicLandType")) {
|
||||
validTypes.addAll(CardType.getBasicTypes());
|
||||
kindOfType = "basic land";
|
||||
} else if (changedTypeWordsArray[0].equals("ChooseCreatureType")) {
|
||||
} else if (changedTypeWordsArray[1].equals("ChooseCreatureType")) {
|
||||
validTypes.addAll(CardType.Constant.CREATURE_TYPES);
|
||||
kindOfType = "creature";
|
||||
}
|
||||
|
||||
@@ -215,6 +215,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
game.getTriggerHandler().resetActiveTriggers(false);
|
||||
|
||||
if (!triggerList.isEmpty()) {
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Cards", triggerList);
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
@@ -104,10 +105,6 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
sb.append(Lang.getNumeral(min)).append(" or ").append(list.size() == 2 ? "both" : "more");
|
||||
}
|
||||
|
||||
if (repeat) {
|
||||
sb.append(". You may choose the same mode more than once.");
|
||||
}
|
||||
|
||||
if (sa.hasParam("ChoiceRestriction")) {
|
||||
String rest = sa.getParam("ChoiceRestriction");
|
||||
if (rest.equals("NotRemembered")) {
|
||||
@@ -115,8 +112,17 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
if (repeat) {
|
||||
sb.append(". You may choose the same mode more than once.");
|
||||
}
|
||||
|
||||
boolean additionalDesc = sa.hasParam("AdditionalDescription");
|
||||
if (additionalDesc) {
|
||||
sb.append(" ").append(sa.getParam("AdditionalDescription").trim());
|
||||
}
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
if (!repeat) {
|
||||
if (!repeat && !additionalDesc) {
|
||||
sb.append(" \u2014");
|
||||
}
|
||||
sb.append("\r\n");
|
||||
@@ -150,7 +156,9 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
return;
|
||||
}
|
||||
|
||||
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||
final int num = sa.hasParam("CharmNumOnResolve") ?
|
||||
AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CharmNumOnResolve"), sa)
|
||||
: Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||
|
||||
Card source = sa.getHostCard();
|
||||
@@ -167,7 +175,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
source.setChosenPlayer(chooser);
|
||||
}
|
||||
|
||||
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, min, num, sa.hasParam(("CanRepeatModes")));
|
||||
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, min, num, sa.hasParam("CanRepeatModes"));
|
||||
chainAbilities(sa, chosen);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,11 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
controller = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa).get(0);
|
||||
}
|
||||
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, "Do you want to copy the spell " + card + "?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<SpellAbility> tgtSpells = getTargetSpells(sa);
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user