Compare commits

..

5247 Commits

Author SHA1 Message Date
Blacksmith
5192fc3f59 [maven-release-plugin] prepare release forge-1.6.16 2018-09-29 19:53:10 +00:00
Blacksmith
efca55083c Update README.txt for release 2018-09-29 19:51:13 +00:00
Sol
735a7997d6 Merge branch 'grn-changes' into 'master'
Add GRN and token changes to CHANGES.txt

See merge request core-developers/forge!963
2018-09-29 18:35:56 +00:00
Krazy
3e5636ea21 Merge branch 'migrate_upcoming' into 'master'
Migrate upcoming

See merge request core-developers/forge!962
2018-09-29 18:19:09 +00:00
tehdiplomat
5f5bcc35ad Fix migrateupcoming script and remove scripted reprints 2018-09-29 14:06:29 -04:00
KrazyTheFox
be5c3a0401 Add GRN and token changes to CHANGES.txt 2018-09-29 14:03:44 -04:00
tehdiplomat
17062eecdb Not sure why these reprints got deleted by migrate script? 2018-09-29 14:03:22 -04:00
tehdiplomat
e036465ce3 Migrate GRN to their letter folders 2018-09-29 13:57:51 -04:00
Sol
c52f8efa11 Merge branch 'grnreleasetest' into 'master'
Update to GRN standard deck generation ready for release plus changes.txt update

See merge request core-developers/forge!960
2018-09-29 17:31:22 +00:00
austinio7116
183a671227 Updated Changes.txt to include mention of new random standard quest world
(cherry picked from commit 958671d)
2018-09-29 07:09:42 +01:00
austinio7116
7cc247c6c7 Crackling Drake
(cherry picked from commit 4292012)
2018-09-29 07:09:29 +01:00
austinio7116
a8a69d2a66 Rerun deck generation with just committed GRN cards
(cherry picked from commit 827bd24)
2018-09-29 06:59:53 +01:00
austinio7116
63822c16c1 Updated pre-prerelease Standard LDA deck archetype data for deck generation - assumes new standard format is live
(cherry picked from commit 913cce4)

(cherry picked from commit 0bffd34)
2018-09-29 06:45:17 +01:00
Michael Kamensky
f577523dee Merge branch 'no-quest-npe-fix' into 'master'
Fix a crash that occurred when no quests were available to load

See merge request core-developers/forge!959
2018-09-29 04:24:13 +00:00
Krazy
dca93282f3 Merge branch 'guilds_fxes' into 'master'
Update formats

See merge request core-developers/forge!958
2018-09-29 04:19:20 +00:00
KrazyTheFox
0e9bf4fe4b Fix a crash that occurred when no quests were available to load 2018-09-29 00:07:05 -04:00
tehdiplomat
7cdf7538e9 Update formats 2018-09-28 21:25:25 -04:00
Michael Kamensky
2ab59849a8 Merge branch 'guilds_fxes' into 'master'
Experimental Frenzy

See merge request core-developers/forge!954
2018-09-28 09:27:23 +00:00
Hans Mackowiak
60f1b13110 Merge branch 'patch-13' into 'master'
Changes Tetsuo Umezawa to use the same script as Bartel Runeaxe, instead of a keyworded ability

See merge request core-developers/forge!956
2018-09-28 08:33:05 +00:00
Meerkov
de5d7c4349 Changes Tetsuo Umezawa to use the same script as Bartel Runeaxe, instead of a keyworded ability 2018-09-27 16:48:27 +00:00
tehdiplomat
2f30b14c53 Fix AI passing in null to canPlayLand 2018-09-27 10:57:04 -04:00
tehdiplomat
c1df1da195 Apparently unused imports is a compile error? 2018-09-27 08:21:48 -04:00
Michael Kamensky
9bbd1da0ba Merge branch 'bug-fixes' into 'master'
fix NPE when there is nothing to choose from

See merge request core-developers/forge!952
2018-09-27 04:37:43 +00:00
Michael Kamensky
fc8142db8b Merge branch 'themes' into 'master'
theme related fixes

See merge request core-developers/forge!953
2018-09-27 04:35:18 +00:00
Jamin W. Collins
848df654fa set relative font sizes in Card Detail Panel
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-26 17:28:08 -06:00
Jamin W. Collins
9ab50c4d9d create and use getRelativeFontSize() method
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-26 17:27:15 -06:00
Jamin W. Collins
32ab0aadb0 fix NPE when there is nothing to choose from
With the existing code, if a player cast Surgical Extraction targetting
a card that only existed in the target player's graveyard (no copies in
the players hand or library), an NPE would be thrown.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-26 17:26:03 -06:00
Michael Kamensky
c8ab4ff0c4 Merge branch 'master' into 'master'
Removing SVar:Rarity

See merge request core-developers/forge!951
2018-09-26 10:25:56 +00:00
Michael Kamensky
b125dd9e2d Merge branch 'svarupdate' into 'master'
Update Svar->SVar.

See merge request core-developers/forge!950
2018-09-26 10:24:56 +00:00
Agetian
0e398d2589 - Removing SVar:Rarity:Rare 2018-09-26 13:21:46 +03:00
Agetian
08e3f1ebd5 - Removing SVar:Rarity 2018-09-26 10:59:54 +03:00
Agetian
d273a35305 - Update Svar->SVar. 2018-09-26 09:16:34 +03:00
Michael Kamensky
0663024acf Merge branch 'patch-13' into 'master'
Update crystal_seer.txt

See merge request core-developers/forge!946
2018-09-26 06:14:51 +00:00
Michael Kamensky
3f675c0102 Merge branch 'guildofravnica' into 'master'
Added Etrata the Silencer, including new HIT counters

See merge request core-developers/forge!949
2018-09-26 06:13:24 +00:00
Michael Kamensky
527740c493 Merge branch 'patch-14' into 'master'
Update demon_of_catastrophes.txt to use new "sac as an additional cost" mechanism.

See merge request core-developers/forge!947
2018-09-26 06:07:11 +00:00
Michael Kamensky
231c028d42 Merge branch 'standardQuestMode' into 'master'
Added Randomized Standard Quest World with LDA archetype generated decks as…

See merge request core-developers/forge!890
2018-09-26 05:47:14 +00:00
Michael Kamensky
b8254b6dd9 Merge branch 'themes' into 'master'
Themes

See merge request core-developers/forge!945
2018-09-26 05:44:20 +00:00
Michael Kamensky
64fe052c0f Merge branch 'sentry-bug-fixes' into 'master'
Sentry bug fixes

See merge request core-developers/forge!948
2018-09-26 05:25:58 +00:00
Jamin W. Collins
593218343c avoid CME in chooseSaToActivateFromOpeningHand()
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-25 21:42:22 -06:00
Jamin W. Collins
79267ac29f avoid CME in checkStaticAbilities()
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-25 21:41:38 -06:00
Jamin W. Collins
701418ab63 avoid CME in cleanUpTemporaryReplacements()
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-25 21:26:43 -06:00
tehdiplomat
eefc6ea9d3 Experimental Frenzy 2018-09-25 23:23:46 -04:00
Jamin W. Collins
970931bcfd create a new user preference for default font size
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-25 07:49:23 -06:00
Jamin W. Collins
7781660c11 make font sizes relative to default (12)
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-25 07:49:23 -06:00
Jamin W. Collins
e6af6abfcf adding the Darky theme
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-25 07:49:23 -06:00
austinio7116
848b495b52 Added Etrata the Silencer, including new HIT counters 2018-09-25 08:28:44 +01:00
Meerkov
bd2b3fab9b Update demon_of_catastrophes.txt to use new "sac as an additional cost" mechanism. 2018-09-25 02:45:11 +00:00
Meerkov
5d53321885 Update crystal_seer.txt 2018-09-25 00:40:38 +00:00
Jamin Collins
5a5e1ddac8 Merge branch 'guildofravnica' into 'master'
Some GRN fixes

See merge request core-developers/forge!944
2018-09-25 00:33:27 +00:00
austinio7116
12da68cd77 Whispering Snitch updated to use OnlyFirst rather than an SVar 2018-09-24 19:36:34 +01:00
austinio7116
4f3fe32f89 Corrected Whispering Snitch trigger 2018-09-24 17:26:17 +01:00
austinio7116
6ed4da392e Renamed remaining GRN split cards appropriately 2018-09-24 17:06:54 +01:00
Michael Kamensky
b7f5d6e85e Merge branch 'guilds_fxes' into 'master'
Guilds fxes

See merge request core-developers/forge!941
2018-09-24 04:15:18 +00:00
Michael Kamensky
24e1affd51 Merge branch 'patch-14' into 'master'
Fix another linewrap issue

See merge request core-developers/forge!943
2018-09-24 04:14:14 +00:00
Michael Kamensky
f0d0949ac5 Merge branch 'patch-13' into 'master'
Fix Oracle text line wrap.

See merge request core-developers/forge!942
2018-09-24 04:14:02 +00:00
Meerkov
c3371cf5fa Fix another linewrap issue 2018-09-24 03:52:31 +00:00
Meerkov
c699644a74 Fix Oracle text line wrap. 2018-09-24 03:50:00 +00:00
tehdiplomat
acadff3e04 Fix the Scarab God trigger 2018-09-23 22:56:36 -04:00
tehdiplomat
ac940a6db8 Fix Legion Warboss TokenSCript 2018-09-23 22:44:14 -04:00
tehdiplomat
f6bce3cdb1 More fixes to Pelt Collector 2018-09-23 22:36:09 -04:00
tehdiplomat
ab58ded0a1 Fix Druid of Horns 2018-09-23 22:33:01 -04:00
tehdiplomat
84d3ae83f1 More GUilds fixes 2018-09-23 22:29:19 -04:00
tehdiplomat
52893e54ee Fix cost and name of Response // Resurgence 2018-09-23 22:09:19 -04:00
tehdiplomat
df2028e4f3 Fix Guildmages' Forum 2018-09-23 21:39:07 -04:00
tehdiplomat
8e45262a7a Fix Whispering Snitch 2018-09-23 21:24:37 -04:00
tehdiplomat
ef97991237 Fix Dimir Informant 2018-09-23 21:23:19 -04:00
tehdiplomat
1d89f466ab Fix Concoct 2018-09-23 21:13:18 -04:00
Sol
760c45f9c2 Merge branch 'deck-editor' into 'master'
deck editor fixes

Closes #724

See merge request core-developers/forge!940
2018-09-23 20:22:58 +00:00
Jamin W. Collins
bbf63d9083 fix Quest Draft Editor
Closes: core-developers/forge#724

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-23 13:33:17 -06:00
Jamin W. Collins
781400c0a3 fix Draft mode editor
The recent changes to bring Commander style editors into the normal Deck
Editor tab appear to have broken the drafting editor.  This change set
appears to resolve that issue.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-23 13:04:19 -06:00
Michael Kamensky
9561b75f17 Merge branch 'puzzle-engine' into 'master'
Added puzzles PS_GRN0 and PS_GUEST1.

See merge request core-developers/forge!939
2018-09-23 05:24:52 +00:00
Agetian
f624d76301 - Added puzzles PS_GRN0 and PS_GUEST1. 2018-09-23 08:23:53 +03:00
Michael Kamensky
35b3bc0623 Merge branch 'guilds_fxes' into 'master'
Fixes from Wharrgarble

See merge request core-developers/forge!938
2018-09-23 04:15:27 +00:00
tehdiplomat
939cdb6822 When Planeswalkers aren't on the battlefield, show their base loyalty 2018-09-22 22:58:15 -04:00
tehdiplomat
e4303df238 Fix Niv Mizzet Parun's draw trigger 2018-09-22 22:05:30 -04:00
tehdiplomat
2e09c8230b Dazzling lights spell description 2018-09-22 22:00:50 -04:00
tehdiplomat
c69c81fcd0 Fix Undercity Uprising 2018-09-22 21:58:44 -04:00
tehdiplomat
781aa97546 Fix cost on Find 2018-09-22 21:51:08 -04:00
tehdiplomat
321c6e52c8 Fix Guildgates 2018-09-22 21:39:58 -04:00
tehdiplomat
2d153d58cb Fix Sprouting Renewal 2018-09-22 21:18:07 -04:00
tehdiplomat
a46b05d703 Fixes from Wharrgarble 2018-09-22 21:08:50 -04:00
Michael Kamensky
faa1b1a767 Merge branch 'tokenRE' into 'master'
TokenRE: now can be replaced with TokenScript

See merge request core-developers/forge!936
2018-09-22 18:56:28 +00:00
Hanmac
4508d54543 TokenRE: now can be replaced with TokenScript 2018-09-22 14:33:18 +02:00
Michael Kamensky
65e8fd1842 Merge branch 'guildofravnica' into 'master'
Urban Utopia

See merge request core-developers/forge!933
2018-09-22 03:43:24 +00:00
Michael Kamensky
fc082f77af Merge branch 'more_guilds_fixes' into 'master'
More guilds fixes

See merge request core-developers/forge!935
2018-09-22 03:42:50 +00:00
tehdiplomat
2435767504 Fix Aurelia 2018-09-21 22:49:51 -04:00
tehdiplomat
744c766852 Convert tokens to script 2018-09-21 22:49:39 -04:00
Michael Kamensky
07e43131c2 Merge branch 'master' into 'master'
Preparing Forge for Android publish 1.6.15.003 [incremental].

See merge request core-developers/forge!934
2018-09-21 19:18:17 +00:00
Agetian
a1e87417f0 - Preparing Forge for Android publish 1.6.15.003 [incremental]. 2018-09-21 22:17:49 +03:00
austinio7116
15cd7c4eab Urban Utopia 2018-09-21 19:00:22 +01:00
austinio7116
de224e78fc Merge branch 'master' into 'master'
GRN: Fixed two cards

See merge request core-developers/forge!931
2018-09-21 06:29:24 +00:00
austinio7116
d4324ae9e1 Merge branch 'guildofravnica' into 'master'
GRN fixes

See merge request core-developers/forge!932
2018-09-21 05:31:44 +00:00
austinio7116
a7041fc767 Removed 0 mana cost from lands 2018-09-21 06:30:21 +01:00
swordshine
600f52537e - Fixed two cards 2018-09-21 12:30:39 +08:00
Michael Kamensky
72f28b6c44 Merge branch 'someSmallFixes' into 'master'
Some small fixes

Closes #720, #715, and #691

See merge request core-developers/forge!928
2018-09-21 04:14:12 +00:00
Michael Kamensky
43af9df723 Merge branch 'card_fixes' into 'master'
GRN fixes

See merge request core-developers/forge!930
2018-09-21 04:14:08 +00:00
tehdiplomat
1dcf66175b Skip tokens for edition tracking 2018-09-20 22:49:30 -04:00
tehdiplomat
deff49452c GRN fixes 2018-09-20 22:11:51 -04:00
Sol
0d7328da92 Merge branch 'guildofravnica' into 'master'
Guilds of Ravnica cards, editions file and block data

See merge request core-developers/forge!927
2018-09-21 01:39:10 +00:00
austinio7116
b3750d98d4 final GRN editions file 2018-09-20 22:17:12 +01:00
austinio7116
a702da5d2f Added 2 missing cards - removed experimenetal frenzy for now 2018-09-20 22:15:33 +01:00
austinio7116
12236efa70 Clean up Knight of Autumn description 2018-09-20 14:32:09 +01:00
austinio7116
57a24ae697 Fixe smelt-ward minotaur name 2018-09-20 13:01:19 +01:00
austinio7116
2cd2ae6968 Fix Devious Cover-Up name 2018-09-20 12:55:07 +01:00
austinio7116
3674ebff1b Fix knight of autumn ETB Trigger 2018-09-20 12:48:52 +01:00
austinio7116
3b2fd67257 Removed duplicate card 2018-09-20 08:34:41 +01:00
austinio7116
b6b270f979 Removed duplicate card 2018-09-20 08:33:08 +01:00
austinio7116
54f1fdb453 Update to Pilfering Imp
(cherry picked from commit 78965c1)
2018-09-19 23:11:10 +01:00
austinio7116
3ceec24a86 Added anyplayer to Nullhide Ferox 2018-09-19 22:44:41 +01:00
austinio7116
0f8d711adf Fixed sworn companions missing cost 2018-09-19 22:41:57 +01:00
austinio7116
8a0fc39d16 Fixed dazzling lights 2018-09-19 22:40:12 +01:00
austinio7116
a69cdf37ae First draft of GRN blockdata and draft rankings 2018-09-19 22:32:38 +01:00
austinio7116
64907dda5c Final GRN editions file 2018-09-19 20:53:33 +01:00
austinio7116
80ed37033b Verdani Shieldmate 2018-09-19 20:27:45 +01:00
austinio7116
b1cd294052 Final day spoilers that forgescribe did well on - only a few minor fixes required 2018-09-19 20:26:08 +01:00
Michael Kamensky
2d1bd4d541 Merge branch 'patch-1' into 'master'
Upate cleanUpTemporaryTriggers to prevent Modification Exception

See merge request core-developers/forge!929
2018-09-19 17:46:45 +00:00
Hans Mackowiak
a28c010b59 Upate cleanUpTemporaryTriggers to prevent Modification Exception 2018-09-19 08:02:54 +00:00
austinio7116
c72c288dcd More GRN spoilers 2018-09-19 08:16:01 +01:00
austinio7116
e4c7b5fe93 Tajic Legion's Edge 2018-09-19 07:06:15 +01:00
Hanmac
f322dddbd2 cards: more fixes 2018-09-19 07:32:04 +02:00
austinio7116
b462d76f70 Updated editions file 2018-09-18 23:34:42 +01:00
austinio7116
a1c234b41e More spoilers 2018-09-18 23:02:23 +01:00
Hanmac
5a3cea6bf2 cards: some card fixes 2018-09-18 23:34:51 +02:00
Hanmac
643dfc5092 some small fixes 2018-09-18 23:34:13 +02:00
austinio7116
95b0003ae3 Undercity uprising 2018-09-18 21:18:12 +01:00
austinio7116
e32f311051 Wand of vertebrae 2018-09-18 21:15:21 +01:00
austinio7116
888e2c7376 First attempt at Ledev 2018-09-18 20:26:44 +01:00
austinio7116
0d80650506 More GRN spoilers from forgescribe with some fixes as needed. 2018-09-18 17:30:08 +01:00
Sol
b50f44d10c Merge branch 'guildofravnica' into 'master'
A couple of surveil related text description fixes picked up during testing

See merge request core-developers/forge!926
2018-09-17 13:15:19 +00:00
austinio7116
9a3432e47d A couple of surveil related text description fixes picked up during testing 2018-09-17 10:46:30 +01:00
Jamin Collins
ee97dd44db Merge branch 'guildofravnica' into 'master'
More GRN Spoilers from Forgescribe fixed up but untested

See merge request core-developers/forge!909
2018-09-17 07:52:33 +00:00
austinio7116
2bb6b9e715 Removed unused imports 2018-09-16 22:09:24 +01:00
austinio7116
fd122d392d Cardsmith's fix for goblin cratermaker text 2018-09-16 22:00:26 +01:00
austinio7116
ea4063fe2f Fixed several cards that forgeScribe got wrong first time
Another attempt to fix ^M issue in bash script
2018-09-16 21:55:13 +01:00
austinio7116
b492d61d2b Aurelia as scripted by Cardsmith 2018-09-16 21:21:16 +01:00
maustin
c1fe1de82a Merge branch 'coremaster' into guildofravnica 2018-09-16 21:17:50 +01:00
Michael Kamensky
fb39bce069 Merge branch 'master' into 'master'
Fixed the AI not putting the Najeela trigger on stack if it doesn't want to use it.

See merge request core-developers/forge!925
2018-09-16 16:04:39 +00:00
Agetian
d87e63c9a7 - Fixed the AI not putting the Najeela trigger on stack if it doesn't want to use it. 2018-09-16 19:03:24 +03:00
Hans Mackowiak
8e0e976681 Merge branch 'patch-1' into 'master'
Najeela

See merge request core-developers/forge!923
2018-09-16 07:28:29 +00:00
maustin
9d9557009c Merge remote-tracking branch 'Austin/guildofravnica' into guildofravnica
# Conflicts:
#	forge-gui/res/editions/Guilds of Ravnica.txt
2018-09-15 22:07:31 +01:00
austinio7116
48827f34ee Updated editions file for GRN 2018-09-15 22:06:14 +01:00
austinio7116
a710d862bb More GRN Spoilers 2018-09-15 21:58:45 +01:00
austinio7116
664f95bc69 Some more spoiled GRN cards forgescribed and fixed where needed 2018-09-15 21:58:45 +01:00
austinio7116
2ab7b95c47 Renamed conclave guildmage
Price of Fame first attempt
2018-09-15 21:58:45 +01:00
austinio7116
f4a3fb7daf More GRN Spoilers 2018-09-15 21:58:45 +01:00
austinio7116
4192f9327e Fixed Arclight duplicate count 2018-09-15 21:58:45 +01:00
austinio7116
5c6fe93422 Updated GRN editions file 2018-09-15 21:58:45 +01:00
austinio7116
c2359a512f More GRN cards 2018-09-15 21:58:07 +01:00
austinio7116
45a42ea816 More GRN spoilers 2018-09-15 21:58:07 +01:00
austinio7116
c69e7dce8d More GRN spoilers - guildmages_forum needs special testing attention 2018-09-15 21:58:07 +01:00
austinio7116
c2b8b21a1e Vivid revival and lockets 2018-09-15 21:58:07 +01:00
austinio7116
d1d9910b67 Removed chromatic lantern as it is a reprint 2018-09-15 21:58:07 +01:00
austinio7116
765a15fa6f Expansion Explosion 2018-09-15 21:58:07 +01:00
austinio7116
5d3b0f5f7b Secrets of the mausoleum 2018-09-15 21:58:07 +01:00
austinio7116
5e3bdc18b2 Updated GRN editions file 2018-09-15 21:58:07 +01:00
austinio7116
c528db302b Rain of Notions, NightVeil Faerie 2018-09-15 21:58:07 +01:00
austinio7116
088891604f Fixes for hypothesizzle 2018-09-15 21:58:07 +01:00
austinio7116
f6111b6c85 More GRN spoilers. Nullhide Ferox needs code change to support "any player may activate" 2018-09-15 21:58:07 +01:00
austinio7116
ebd58560ec Fixes for Ral, Izzet Viceroy 2018-09-15 21:58:07 +01:00
austinio7116
5b07e961ac GRN more spoiled cards created with forgeScribe and updated 2018-09-15 21:58:07 +01:00
austinio7116
719e5c9a08 GRN updated editions file 2018-09-15 21:58:07 +01:00
austinio7116
b02a1a9976 GRN - deafening clarion 2018-09-15 21:58:07 +01:00
austinio7116
81f6d6c16e Added Undergrowth to Izona trigger description 2018-09-15 21:58:07 +01:00
austinio7116
09ad92e3c1 More GRN Spoilers from Forgescribe fixed up but untested 2018-09-15 21:58:07 +01:00
austinio7116
237f64226f More GRN Spoilers from Forgescribe fixed up but untested 2018-09-15 21:58:07 +01:00
austinio7116
c5d5e08c44 Merge branch 'surveilReplace' into 'master'
Surveil replace

See merge request core-developers/forge!924
2018-09-15 18:32:13 +00:00
austinio7116
b7b302c4f9 More GRN Spoilers 2018-09-15 18:16:28 +01:00
Hanmac
3277377ed6 cards: add some surveil cards 2018-09-15 18:34:40 +02:00
Hanmac
ee9f79a514 Surveil: add ReplaceEffect for adding +2 2018-09-15 18:33:57 +02:00
Hans Mackowiak
c3579709ee Merge branch 'grn_tokens' into 'master'
Convert GRN tokens to TokenScript

See merge request core-developers/forge!902
2018-09-15 15:19:24 +00:00
Sol
ae96dadd54 Convert GRN tokens to TokenScript 2018-09-15 15:19:24 +00:00
Agetian
5503897911 - Added a basic AI logic for Najeela, the Blade Blossom. 2018-09-14 12:51:29 +03:00
Michael Kamensky
2433cf3dad Merge branch 'heavenly-blademaster' into 'master'
Added Heavenly Blademaster (fixed script originally by Austinio).

See merge request core-developers/forge!922
2018-09-14 09:16:02 +00:00
austinio7116
5eba12b029 Some more spoiled GRN cards forgescribed and fixed where needed 2018-09-14 07:59:38 +01:00
Sol
c1f3a49ea0 Najeela 2018-09-14 00:42:11 +00:00
Agetian
bb9cf0903b - Fixed a script line. 2018-09-13 15:54:48 +03:00
Agetian
9354182ba4 - Added Heavenly Blademaster (fixed script originally by Austinio). 2018-09-13 15:51:49 +03:00
Michael Kamensky
5a500b857b Merge branch 'checkstyle' into 'master'
implement basic checkstyle configuration

See merge request core-developers/forge!911
2018-09-13 12:45:22 +00:00
Michael Kamensky
27237070ae Merge branch 'master' into 'master'
Fix a NPE in TokenAi when processing non-creature old style tokens.

See merge request core-developers/forge!919
2018-09-13 11:17:13 +00:00
Michael Kamensky
8c13c526a0 Merge branch 'surveilTrigger' into 'master'
Surveil Trigger with OnlyFirst

See merge request core-developers/forge!917
2018-09-13 11:15:32 +00:00
Michael Kamensky
dedbfbf7b3 Merge branch 'puzzle-engine' into 'master'
- Clarification for PS_M199.

See merge request core-developers/forge!921
2018-09-13 11:15:04 +00:00
Agetian
88551a2107 - Clarification for PS_M199. 2018-09-13 14:13:56 +03:00
Michael Kamensky
82f1291aa6 Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_M199.

See merge request core-developers/forge!920
2018-09-13 11:13:15 +00:00
Agetian
c03004adaf - Added puzzle PS_M199. 2018-09-13 14:12:35 +03:00
Agetian
a9e100cac5 - Fix a NPE in TokenAi when processing non-creature old style tokens. 2018-09-13 14:06:02 +03:00
Michael Kamensky
b48bea304d Merge branch 'master' into 'master'
Basic SurveilAi logic

See merge request core-developers/forge!918
2018-09-13 11:05:18 +00:00
austinio7116
6899fb373f Renamed conclave guildmage
Price of Fame first attempt
2018-09-13 11:21:40 +01:00
Agetian
6d599d2b8c - Basic SurveilAi logic, largely inspired with the ScryAi logic. 2018-09-13 11:27:56 +03:00
austinio7116
8b6183582a More GRN Spoilers 2018-09-13 09:19:33 +01:00
Hanmac
612165ba25 Surveil Trigger with OnlyFirst 2018-09-13 08:15:51 +02:00
austinio7116
6f8ff8200c Fixed Arclight duplicate count 2018-09-12 23:03:08 +01:00
Michael Kamensky
6b6629c01c Merge branch 'master' into 'master'
Preparing Forge for Android publish 1.6.15.002 [hotfix].

See merge request core-developers/forge!916
2018-09-12 19:47:47 +00:00
Agetian
e1d34ef9d5 - Preparing Forge for Android publish 1.6.15.002 [hotfix]. 2018-09-12 22:47:12 +03:00
Michael Kamensky
71397ec648 Merge branch 'sentry-bug-fixes' into 'master'
Sentry Bug Fixes

Closes #705

See merge request core-developers/forge!914
2018-09-12 19:36:05 +00:00
Michael Kamensky
27d2ba384d Merge branch 'multi-select' into 'master'
Multi select

Closes #703

See merge request core-developers/forge!913
2018-09-12 19:35:39 +00:00
Michael Kamensky
37cbd2fe64 Merge branch 'user-reported-bugs' into 'master'
fix Jubilant Mascot creature type

Closes #706

See merge request core-developers/forge!915
2018-09-12 19:28:04 +00:00
austinio7116
85c1ca232e Updated GRN editions file 2018-09-12 19:32:12 +01:00
austinio7116
efd5efb0d5 More GRN cards 2018-09-12 19:26:31 +01:00
austinio7116
aec4726aa2 More GRN spoilers 2018-09-12 18:22:37 +01:00
austinio7116
2b3d1727ea More GRN spoilers - guildmages_forum needs special testing attention 2018-09-12 17:26:06 +01:00
austinio7116
d70778b5be Vivid revival and lockets 2018-09-12 17:06:33 +01:00
austinio7116
f519a2ff6d Removed chromatic lantern as it is a reprint 2018-09-12 16:59:59 +01:00
austinio7116
bb76b63f82 Expansion Explosion 2018-09-12 16:58:37 +01:00
Jamin W. Collins
5c723d8883 log malformed card rather than throw an exception
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-12 07:40:31 -06:00
Jamin W. Collins
933280ce32 fix Jubilant Mascot creature type
Closes: core-developers/forge#706

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-12 07:25:32 -06:00
Jamin W. Collins
841cd22ae4 default importing DeckSection to Main
Closes: core-developers/forge#705

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-11 20:56:40 -06:00
Jamin Collins
d011f9948d Merge branch 'sentryKeyword' into 'master'
Sentry keyword

See merge request core-developers/forge!910
2018-09-12 01:13:42 +00:00
Jamin W. Collins
1da18cb9f5 fix human multi-select on mobile builds
Closes: core-developers/forge#703

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-11 18:36:13 -06:00
Jamin W. Collins
a6423b82f0 correct the new default messaging format
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-11 18:30:19 -06:00
Jamin W. Collins
fc62a41e46 implement basic checkstyle configuration
Remove all unused or redundant imports

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-11 18:16:43 -06:00
austinio7116
2a08ea7092 Secrets of the mausoleum 2018-09-11 22:56:58 +01:00
austinio7116
977f3c2808 Updated GRN editions file 2018-09-11 22:25:04 +01:00
austinio7116
2853e44d6a Rain of Notions, NightVeil Faerie 2018-09-11 22:23:24 +01:00
austinio7116
b1bae53be8 Fixes for hypothesizzle 2018-09-11 20:58:57 +01:00
austinio7116
65dad45e77 More GRN spoilers. Nullhide Ferox needs code change to support "any player may activate" 2018-09-11 20:23:06 +01:00
austinio7116
e36245b414 Fixes for Ral, Izzet Viceroy 2018-09-11 18:41:54 +01:00
austinio7116
568104bbcc GRN more spoiled cards created with forgeScribe and updated 2018-09-11 17:09:14 +01:00
austinio7116
5b02986586 GRN updated editions file 2018-09-11 12:58:29 +01:00
austinio7116
0f5b1c1349 GRN - deafening clarion 2018-09-11 12:55:30 +01:00
Hanmac
998176a7d7 Keyword: use Sentry in createTraits 2018-09-11 10:32:46 +02:00
Hanmac
1cac53b4f0 update Project files for newer eclipse 2018-09-11 10:31:33 +02:00
austinio7116
b18a99c2c5 Added Undergrowth to Izona trigger description 2018-09-11 07:42:19 +01:00
austinio7116
8adfe15b4c More GRN Spoilers from Forgescribe fixed up but untested 2018-09-11 07:40:21 +01:00
Michael Kamensky
9e0a4ded01 Merge branch 'surveil-basic-ai' into 'master'
A simple hook that calls Scry logic for Surveil (with an additional check)

See merge request core-developers/forge!907
2018-09-11 05:35:21 +00:00
austinio7116
c57bb0c349 More GRN Spoilers from Forgescribe fixed up but untested 2018-09-11 06:28:48 +01:00
Sol
fee2312bc8 Merge branch 'deck-editor' into 'master'
Deck Editor changes

Closes #554 and #696

See merge request core-developers/forge!896
2018-09-11 01:26:27 +00:00
Jamin W. Collins
3985d538ae support other sections than just Main/Side
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-10 18:20:15 -06:00
Jamin W. Collins
58a1ad2dd0 create new model before potentially adding cards
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-10 18:19:43 -06:00
austinio7116
4413664b38 Merge branch 'guildofravnica' into 'master'
Corrected Vraska, Regal Gorgon name error due to scryfall typo in Vraska's Stoneglare oracle text.

See merge request core-developers/forge!908
2018-09-10 16:18:22 +00:00
austinio7116
3b6baa65ad Corrected Vraska, Regal Gorgon name error due to scryfall typo in Vraska's Stoneglare oracle text. 2018-09-10 17:16:40 +01:00
Agetian
ac3c6c3d21 - Minor typo fix in GameLogFormatter related to Surveil. 2018-09-10 18:11:59 +03:00
Michael Kamensky
6bca50b1c8 Merge branch 'guildofravnica' into 'master'
Guilds of ravnica - planeswalker deck cards

See merge request core-developers/forge!903
2018-09-10 15:01:42 +00:00
Agetian
bd8fea4616 - Added a scry logic hook for willPutCardOnTop. 2018-09-10 17:56:29 +03:00
austinio7116
55f1e1d41a Fixed missing brackets in oracles, missing planeswalker names in descriptions and missed damage amount 2018-09-10 06:35:05 +01:00
Jamin W. Collins
df91964573 add dated serial to layout file
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:27 -06:00
Jamin W. Collins
33d7a0adcc move Brawl next to Commander
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:27 -06:00
Jamin W. Collins
66fa0c5820 ensure deck name references are cleared on New Deck
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:27 -06:00
Jamin W. Collins
749eea6f57 default to creating a new deck of a given gameType
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:27 -06:00
Jamin W. Collins
f0830c8595 reflow a few oneline if statements
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
6a4423a476 automatically edit preferred deck on tab load
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
0577059909 ensure Brawl deck preference is saved
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
3fcb03d3ed support deleting Brawl, Commander, and TL decks
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
5ab2efdc05 reflow DeckManager deleteDeck
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
c4cf7d3c99 rename All Decks deck listing to Constructed
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
5472421765 remove additional tabs when creating special editor
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
e5281987a5 catch unlikely null sectionMode
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
78b02c08db remove old Commander, Brawl, TL deck editor route
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
29263d2e2d Add Commmander, Brawl, and TL to Deck Editor
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
3f6a02b4b8 add Commander and Brawl to DeckManager case
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
2a48819d2b reflow DeckManager case statement
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
c4683677b3 Add commander decks to normal Deck Editor tab
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Jamin W. Collins
cad9139e9d deck section dropdown
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-09 10:00:26 -06:00
Agetian
5c8acadb5d - Minor correction to match the expected result per description. 2018-09-09 18:58:04 +03:00
Agetian
0c38c6306f - A simple hook that calls Scry logic for Surveil (but on top of that, checks whether there are enough cards in the library not to deck yourself).
- Can be improved further by completely differentiating the logic between the two.
2018-09-09 18:55:58 +03:00
Michael Kamensky
1fa91ecb0f Merge branch 'master' into 'master'
Preparing Forge for Android v1.6.15.001 [incremental/new release].

See merge request core-developers/forge!906
2018-09-09 06:46:35 +00:00
Agetian
11d53bce48 - Preparing Forge for Android v1.6.15.001 [incremental/new release]. 2018-09-09 09:45:55 +03:00
austinio7116
27c9fbc0c9 Missing cost in Ral 2018-09-09 07:26:48 +01:00
austinio7116
7231700ede Fixed counter type on Vraska ultimate 2018-09-09 07:18:03 +01:00
Michael Kamensky
810d55931d Merge branch 'fixups' into 'master'
Fixups

Closes #680, #676, and #673

See merge request core-developers/forge!905
2018-09-09 05:27:21 +00:00
Jamin W. Collins
e4b146128c avoid an NPE when no cards are chosen
Closes: core-developers/forge#680

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-08 10:56:59 -06:00
Jamin W. Collins
353dc885c1 gracefully handle port already in use
Closes: core-developers/forge#676

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-08 10:39:28 -06:00
Jamin Collins
c54ca4c423 Merge branch 'surveil' into 'master'
Surveil

Closes #681 and #684

See merge request core-developers/forge!904
2018-09-08 15:22:28 +00:00
Jamin W. Collins
32df03d045 fix an error in the Human multi-select logic
Closes: core-developers/forge#673

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-08 09:16:03 -06:00
Hanmac
d8f5b870a7 cards: update for surveil 2018-09-08 15:50:45 +02:00
Hanmac
61a627a36a Surveil Effect 2018-09-08 15:50:32 +02:00
austinio7116
31b1781178 GRN planeswalker deck edition update 2018-09-08 07:05:51 +01:00
austinio7116
d0640191e3 Fixes to GRN planeswalker deck scripts 2018-09-08 06:51:40 +01:00
austinio7116
9d04e42a3a GRN planeswalker deck scripts by forgeScribe - unedited 2018-09-08 06:31:59 +01:00
Sol
0baaef6143 Merge branch 'guildofravnica' into 'master'
Guilds of ravnica

See merge request core-developers/forge!897
2018-09-08 01:33:45 +00:00
Blacksmith
5e08fd7082 Clear out release files in preparation for next release 2018-09-08 00:29:57 +00:00
Blacksmith
e7d09e8918 [maven-release-plugin] prepare for next development iteration 2018-09-08 00:24:29 +00:00
Blacksmith
c2f62998fc [maven-release-plugin] prepare release forge-1.6.15 2018-09-08 00:24:26 +00:00
Blacksmith
19feee4de5 Update README.txt for release 2018-09-08 00:22:40 +00:00
austinio7116
4c816fe4af Fixed random quest performance issue.
Switched to 20 life for random challenge AI
Limiting random extra cards to permanents
2018-09-07 17:56:34 +01:00
austinio7116
c136e7c946 Removed testing variable 2018-09-07 14:39:41 +01:00
austinio7116
7ebd5ef70e Added randomly generated quest challenges to the random standard quest world 2018-09-07 14:38:36 +01:00
Blacksmith
e59015357c Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-07 09:12:14 +00:00
Michael Kamensky
31e2d434cd Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_M198.

See merge request core-developers/forge!901
2018-09-07 09:04:47 +00:00
Agetian
950fd116e7 - Added puzzle PS_M198. 2018-09-07 12:04:13 +03:00
Blacksmith
8818711c0f Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-06 03:10:16 +00:00
Sol
58ee5a12eb Merge branch 'xstream' into 'master'
XStream Security Settings

See merge request core-developers/forge!868
2018-09-06 03:04:26 +00:00
Blacksmith
51d49735c2 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-06 01:25:17 +00:00
Jamin Collins
925ae63071 Merge branch 'token_phase_two' into 'master'
Add more support for migrating Editions to TokenScripts

See merge request core-developers/forge!885
2018-09-06 01:16:27 +00:00
austinio7116
d07c4d19e0 Updated GRN edition file 2018-09-05 12:21:45 +01:00
austinio7116
adf48de86b Completed Healer's Hawk 2018-09-05 12:18:32 +01:00
maustin
01c34d498a Merge remote-tracking branch 'Austin/guildofravnica' into guildofravnica
# Conflicts:
#	forge-gui/res/cardsfolder/upcoming/necrotic_wound.txt
#	forge-gui/res/cardsfolder/upcoming/quasiduplicate.txt
2018-09-05 12:15:31 +01:00
austinio7116
4188559b1a Manual fixes to forgeScribe scripts 2018-09-05 12:14:15 +01:00
austinio7116
10a68e9613 More ravnica cards as scripted by forgeScribe4.1 - unedited 2018-09-05 12:04:19 +01:00
austinio7116
89c4042042 Corrected Jump-Start and Surveil syntax 2018-09-05 09:01:13 +01:00
austinio7116
d3fb9f3782 Cleanup of forgeScribe scripts including replacing new keywords where required. 2018-09-05 09:00:05 +01:00
austinio7116
3fc1950a19 Initial GRN ForgeScribe scripts from first spoilers 2018-09-05 08:59:56 +01:00
austinio7116
d5d1320c6e Fixed ^M bad interpeter issue in bash script 2018-09-05 08:59:38 +01:00
Blacksmith
5ffcfe52d8 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-05 06:01:09 +00:00
Michael Kamensky
8a1989ef02 Merge branch 'replaceDying' into 'master'
Replace dying

See merge request core-developers/forge!900
2018-09-05 05:51:49 +00:00
Hanmac
b0e3ff7b45 add necrotic wound 2018-09-05 07:28:31 +02:00
Hanmac
f0c850aa78 update flaying tendrils 2018-09-05 07:25:29 +02:00
Hanmac
af76c37cbb ReplaceDying moved to SpellAbilityEffect 2018-09-05 07:24:55 +02:00
Blacksmith
be32b44feb Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-05 05:01:08 +00:00
Michael Kamensky
e3b3db7471 Merge branch 'jumpstart2' into 'master'
Jump-start: it is more an additional cost like retrace

See merge request core-developers/forge!899
2018-09-05 04:47:48 +00:00
Blacksmith
dc8e811352 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-05 01:01:09 +00:00
Sol
50a2ee70b6 Merge branch 'sentry' into 'master'
Implement Sentry

Closes #650

See merge request core-developers/forge!889
2018-09-05 00:59:46 +00:00
Hanmac
3802b88487 Jump-start: it is more an additional cost like retrace 2018-09-04 21:53:18 +02:00
Blacksmith
351d15701f Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-04 07:16:09 +00:00
Michael Kamensky
64b5b143bf Merge branch 'jumpstart' into 'master'
Keyword: Jump-start

See merge request core-developers/forge!898
2018-09-04 07:04:13 +00:00
Agetian
36132359a7 - Fixed Jump-start description. 2018-09-04 10:01:31 +03:00
Hanmac
7dedaeb111 Keyword: Jump-start 2018-09-04 07:19:58 +02:00
tehdiplomat
8338f62603 More breadcrumbs 2018-09-03 11:45:59 -04:00
tehdiplomat
e4e33ebac0 Fix conflicts that got missed previously 2018-09-03 11:45:23 -04:00
tehdiplomat
e601a6deae Some breadcrumbs for future Token Script development 2018-09-03 11:38:13 -04:00
tehdiplomat
d220f5fb78 Fetch token script images even if they aren't in the token image file 2018-09-03 11:37:44 -04:00
tehdiplomat
67edb87ccd Allow Edition files to accept token names 2018-09-03 11:37:12 -04:00
tehdiplomat
780902ac31 Allow TokenAI to handle TokenScripts 2018-09-03 11:36:41 -04:00
austinio7116
1f001dd354 Corrected Jump-Start and Surveil syntax 2018-09-03 09:37:34 +01:00
maustin
d0cea5151e Merge remote-tracking branch 'Austin/guildofravnica' into guildofravnica
# Conflicts:
#	forge-gui/res/cardsfolder/upcoming/legion_warboss.txt
2018-09-03 09:06:45 +01:00
austinio7116
6540153a53 Cleanup of forgeScribe scripts including replacing new keywords where required. 2018-09-03 09:05:02 +01:00
austinio7116
637ef1e03a Initial GRN ForgeScribe scripts from first spoilers 2018-09-03 09:04:35 +01:00
austinio7116
96459652a1 Fixed ^M bad interpeter issue in bash script 2018-09-03 09:04:14 +01:00
austinio7116
35c52e0e61 Cleanup of forgeScribe scripts including replacing new keywords where required. 2018-09-03 08:54:58 +01:00
austinio7116
a664b7dad9 Initial GRN ForgeScribe scripts from first spoilers 2018-09-03 08:43:47 +01:00
austinio7116
6d7c6f2153 Fixed ^M bad interpeter issue in bash script 2018-09-03 08:40:02 +01:00
Blacksmith
1c517356cf Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-03 03:54:33 +00:00
Michael Kamensky
0baf5ca772 Merge branch 'mentor' into 'master'
Mentor

See merge request core-developers/forge!894
2018-09-03 03:48:38 +00:00
Blacksmith
1a58970c73 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-03 03:39:34 +00:00
Michael Kamensky
b370882b86 Merge branch 'fixups' into 'master'
add TD1 to special rounding case

See merge request core-developers/forge!895
2018-09-03 03:37:35 +00:00
Jamin W. Collins
19be7f51ae configure XStream security for QuestDataIO
The printing of the exception caught in FControl is very helpful for any
future issues caused by the security settings as it indicates which
class was present in the stream, but not allowed.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:44:37 -06:00
Jamin W. Collins
efcee72780 configure XStream security for QuestPetStorage
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:44:37 -06:00
Jamin W. Collins
0e2f47dc8c configure XStream security for QuestBazaarManager
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:44:37 -06:00
Jamin W. Collins
9b6f76eb16 configure XStream security for TournamentIO
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:44:36 -06:00
Jamin W. Collins
cc044978fb configure XStream security for GauntletIO
Catching the ConversionException prevents Gauntlet saves from being
deleted in the event of improper or incomplete security settings on
XStream.  The null check in CSubmenuGauntletContests avoids an exception
should there be no Gauntlet saves.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:44:36 -06:00
Jamin W. Collins
05aac0e1cb update mobile for Sentry
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:30:38 -06:00
Jamin W. Collins
881bd7adf2 add more information to Sentry reports
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:30:38 -06:00
Jamin W. Collins
b0e2450a0e initial Sentry integration
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:30:38 -06:00
Jamin W. Collins
6c99a911f2 add TD1 to special rounding case
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-09-02 11:27:15 -06:00
Hanmac
4b1de970f9 cards: add mentor example 2018-09-02 18:14:41 +02:00
Hanmac
e98431e525 CardFactoryUtil: add Mentor Keyword 2018-09-02 18:14:05 +02:00
Blacksmith
7d6e8e95d3 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-02 15:54:34 +00:00
Michael Kamensky
b56b90ec1a Merge branch 'bbdforgescribe' into 'master'
Some BBD cards scripted by forgeScribe (with a bit of cleanup on a couple)

See merge request core-developers/forge!893
2018-09-02 15:43:39 +00:00
Michael Kamensky
5a81be04a6 Merge branch 'prowlFix' into 'master'
Prowl fix

See merge request core-developers/forge!892
2018-09-02 15:39:52 +00:00
austinio7116
ab209d6db5 Some BBD cards scripted by forgeScribe (with a bit of cleanup on a couple) 2018-09-02 16:21:00 +01:00
Hanmac
724e4d5932 cards: update Prowl cards 2018-09-02 14:47:06 +02:00
Hanmac
6ec0dd49d7 Prowl is a Keyword now 2018-09-02 14:45:56 +02:00
Blacksmith
c67b10dee6 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-09-01 14:09:34 +00:00
Michael Kamensky
a1ebb1f3df Merge branch 'm19recentfixes' into 'master'
Completed Tezzeret's Gatebreaker script (with a little help from forgeScribe)

See merge request core-developers/forge!891
2018-09-01 13:55:06 +00:00
austinio7116
f5add1bc8f Completed Tezzeret's Gatebreaker script (with a little help from forgeScribe) 2018-09-01 08:41:49 +01:00
maustin
67aaf75973 Merge remote-tracking branch 'Austin/standardQuestMode' into standardQuestMode 2018-09-01 06:33:15 +01:00
austinio7116
952e08848c Added Randomized Standard Quest World with LDA archetype generated decks as duels and standard card pool 2018-09-01 06:32:52 +01:00
austinio7116
2ab982463e Added Randomized Standard Quest World with LDA archetype generated decks as duels and standard card pool 2018-08-31 23:00:59 +01:00
Blacksmith
43338bd0a3 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-30 20:44:18 +00:00
Michael Kamensky
2476bcfa31 Merge branch 'surgedFix' into 'master'
Surge: don't use OptionalCost there

See merge request core-developers/forge!887
2018-08-30 19:29:15 +00:00
Hanmac
f967d9885a CardProperty: fixed surged 2018-08-30 21:18:08 +02:00
Michael Kamensky
83721cadbd Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_M197.

See merge request core-developers/forge!888
2018-08-30 19:10:46 +00:00
Agetian
b22c1ece86 - Added puzzle PS_M197. 2018-08-30 22:10:10 +03:00
Hanmac
73b94a63a5 Surge: don't use OptionalCost there 2018-08-30 20:25:30 +02:00
Blacksmith
2bcd57910e Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-30 11:20:42 +00:00
Michael Kamensky
b279ae0771 Merge branch 'import_deck' into 'master'
Enables `Import` `Open` `New deck` `Save As` buttons in all deck editors with finite catalog

See merge request core-developers/forge!884
2018-08-30 11:18:09 +00:00
Blacksmith
51735b9ff7 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-30 05:50:42 +00:00
Michael Kamensky
2f1527d89f Merge branch 'fix-xantcha' into 'master'
Xantcha is an "As" not a "When"

See merge request core-developers/forge!886
2018-08-30 05:45:21 +00:00
Sol
0ded352f22 Xantcha is an "As" not a "When" 2018-08-29 18:50:38 +00:00
NikolayHD
66e52cbd6a Support Import, Open, New deck, Save As
in all deck editors with finite catalog
2018-08-29 01:15:27 +03:00
Blacksmith
aec894a569 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-27 16:50:42 +00:00
Michael Kamensky
db7ab85f26 Merge branch 'multi-select' into 'master'
properly message and limit human multi-select

See merge request core-developers/forge!882
2018-08-27 16:39:44 +00:00
Jamin W. Collins
5f6ea460dd properly message and limit human multi-select
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-27 08:18:40 -06:00
Blacksmith
97f93418bf Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-27 03:20:42 +00:00
Sol
42acde1ba0 Merge branch 'android-network-play' into 'master'
Android network play

See merge request core-developers/forge!836
2018-08-27 03:07:21 +00:00
Jamin W. Collins
095bb4a154 make protocol definition match implementation
com.badlogic.gdx.utils.GdxRuntimeException: java.lang.InternalError: Protocol method getActivateDescription: illegal argument (0) of type forge.game.card.CardView, java.lang.String expected
	at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:127)
Caused by: java.lang.InternalError: Protocol method getActivateDescription: illegal argument (0) of type forge.game.card.CardView, java.lang.String expected
	at forge.net.ProtocolMethod.checkArgs(ProtocolMethod.java:158)
	at forge.net.GameProtocolSender.sendAndWait(GameProtocolSender.java:21)
	at forge.net.client.NetGameController.sendAndWait(NetGameController.java:28)
	at forge.net.client.NetGameController.getActivateDescription(NetGameController.java:93)
	at forge.screens.match.views.VCardDisplayArea.getActivateAction(VCardDisplayArea.java:186)
	at forge.card.CardZoom.onCardChanged(CardZoom.java:113)
	at forge.card.CardZoom.show(CardZoom.java:64)
	at forge.screens.match.views.VCardDisplayArea$CardAreaPanel.showZoom(VCardDisplayArea.java:406)
	at forge.screens.match.views.VCardDisplayArea$CardAreaPanel.longPress(VCardDisplayArea.java:396)
	at forge.Forge$MainInputProcessor.longPress(Forge.java:575)
	at forge.toolbox.FGestureAdapter$1.run(FGestureAdapter.java:43)
	at com.badlogic.gdx.backends.lwjgl.LwjglApplication.executeRunnables(LwjglApplication.java:246)
	at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:199)
	at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:120)

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-26 12:56:27 -06:00
Blacksmith
e70f6da283 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-26 17:35:42 +00:00
Jamin W. Collins
97e6657bfc move everything to guava android for compatibility
There appear to be incompatibilities between guava 24.1-jre and
24.1-android with regard to serialization.  As long as networking
is dependent upon serialization desktop and android need to use
the exact same guava package.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-26 11:26:38 -06:00
Jamin W. Collins
071c45839d add Dev Mode indication
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-26 11:26:38 -06:00
Jamin W. Collins
39c002e903 only the specific player can adjust their team
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-26 11:26:38 -06:00
Jamin W. Collins
57d5013750 only network host can adjust number of players
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-26 11:26:38 -06:00
Michael Kamensky
11ca1231a4 Merge branch 'dredgeFix' into 'master'
Dredge use :

See merge request core-developers/forge!881
2018-08-26 17:24:35 +00:00
Hanmac
9acb1dca93 Dredge use : 2018-08-26 15:20:20 +02:00
Blacksmith
5fec8ed29d Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-26 09:35:42 +00:00
Michael Kamensky
3dda238f7a Merge branch 'updatedmoderndeckgenm19' into 'master'
Updated modern LDA deckgen data

See merge request core-developers/forge!880
2018-08-26 09:27:17 +00:00
austinio7116
017f84c056 Updated modern LDA deckgen data 2018-08-26 08:01:22 +01:00
Blacksmith
97d4fef0bf Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-26 04:05:43 +00:00
Michael Kamensky
c5d853d5af Merge branch 'fixups' into 'master'
catch and record benign network play exception

See merge request core-developers/forge!877
2018-08-26 03:57:59 +00:00
Michael Kamensky
f7c7159942 Merge branch 'multi-select' into 'master'
fix human player multi-select

See merge request core-developers/forge!878
2018-08-26 03:57:21 +00:00
Jamin W. Collins
eaeb2838e0 fix human player multi-select
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-25 05:51:07 -06:00
Jamin W. Collins
f500f6837f catch and record benign network play exception
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-24 20:21:13 -06:00
Blacksmith
a9292924ae Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-08-23 16:35:42 +00:00
Sol
fb5710eeec Merge branch 'EditionTrackingFix' into 'master'
Update EditionTracking.py to work with new formats directory structure

See merge request core-developers/forge!876
2018-08-23 16:24:44 +00:00
Mitchell Matthews
65282bee6a Update EditionTracking.py to work with new formats directory structure 2018-08-23 12:22:17 -04:00
Mitchell Matthews
0a3532d4c5 Update EditionTracking.py to work with new formats directory structure 2018-08-23 11:54:47 -04:00
Michael Kamensky
f217b3da3a Merge branch 'new_cards' into 'master'
Adding Xantcha and Spellseeker

See merge request core-developers/forge!872
2018-08-22 16:20:20 +00:00
tehdiplomat
83254033fb Add description for Xantcha 2018-08-22 12:13:24 -04:00
Michael Kamensky
2c3e7257c7 Merge branch 'rampageFix' into 'master'
CardFactoryUtil: Rampage use :

See merge request core-developers/forge!874
2018-08-22 16:10:14 +00:00
tehdiplomat
746c8b7d8f Fix Xantcha activated ability 2018-08-22 11:00:59 -04:00
Hanmac
367875c15c CardFactoryUtil: Rampage use : 2018-08-22 12:37:59 +02:00
Michael Kamensky
1254a03782 Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_M196.

See merge request core-developers/forge!873
2018-08-22 04:08:20 +00:00
Agetian
85a386d58d - Added puzzle PS_M196. 2018-08-22 07:07:40 +03:00
tehdiplomat
afc3182aa0 Adding Xantcha and Spellseeker 2018-08-21 21:03:17 -04:00
Michael Kamensky
e968afbd87 Merge branch 'timestampStateFix' into 'master'
Timestamp state fix

Closes #594 and #647

See merge request core-developers/forge!860
2018-08-21 20:26:42 +00:00
Michael Kamensky
95e9a0bcc8 Merge branch 'EquipInstantSpeed' into 'master'
GameActionUtil: EquipInstantSpeed should affect the player

See merge request core-developers/forge!871
2018-08-21 16:41:40 +00:00
Michael Kamensky
78a090dfd9 Merge branch 'newPerspectivesCyclingFix' into 'master'
GameActionUtil: fix CyclingForZero

Closes #653

See merge request core-developers/forge!870
2018-08-21 16:21:16 +00:00
Hanmac
2603d14b7e GameActionUtil: EquipInstantSpeed should affect the player 2018-08-21 17:43:17 +02:00
Hanmac
ed04025b3b GameActionUtil: fix CyclingForZero 2018-08-21 15:18:49 +02:00
Michael Kamensky
858642510f Merge branch 'm19deckgenupdate2' into 'master'
Latest LDA data for Standard - more archetypes and more data

See merge request core-developers/forge!869
2018-08-20 04:41:25 +00:00
austinio7116
abb1ec890a Latest LDA data for Standard - more archetypes and more data 2018-08-19 06:39:03 +01:00
Hanmac
04202f0fb4 WrappedAbility: exclude Token from WrappedCheck 2018-08-18 19:39:00 +02:00
Hanmac
68e40254f8 CardFactoryUtil: more LKI for Triggered Keywords 2018-08-18 11:11:25 +02:00
Hanmac
fb3e0f1445 WrappedAbility: return timestampCheck but with extra checks 2018-08-18 08:46:38 +02:00
Hanmac
3aa5d191fa cards: add some LKI to some DealDamage effects 2018-08-18 08:46:38 +02:00
Hanmac
0f3ac8c212 WrappedAbility: remove faulty check 2018-08-18 08:46:38 +02:00
Hanmac
5f385f8a40 cards: update cards 2018-08-18 08:46:38 +02:00
Hanmac
baf4a17ceb Effects: add gameCardState check with equalWithTimestamp 2018-08-18 08:46:38 +02:00
Hanmac
e2c33b089c Card: fix equalsWithTimestamp and timestamp of LKI 2018-08-18 08:46:38 +02:00
Michael Kamensky
97d0e4f1b7 Merge branch 'moar_c18_cards' into 'master'
Add the right conditional for Yennett

See merge request core-developers/forge!867
2018-08-18 04:13:52 +00:00
tehdiplomat
63c06ed7cb Add the right conditional for Yennett 2018-08-17 22:11:02 -04:00
Sol
f7230cbff3 Update Commander Anthology Vol. II.txt 2018-08-17 19:51:38 +00:00
Michael Kamensky
8627ecb165 Merge branch 'afflictFix' into 'master'
Afflict use :

See merge request core-developers/forge!866
2018-08-17 15:49:52 +00:00
Hanmac
553a794642 Afflict use : 2018-08-17 17:31:49 +02:00
Michael Kamensky
bf3ce5b68a Merge branch 'feature-request' into 'master'
only display the ante tab when playing for ante

Closes #381

See merge request core-developers/forge!863
2018-08-17 15:25:07 +00:00
Michael Kamensky
48a7e50bfa Merge branch 'fortifyFix' into 'master'
Fortify use :

See merge request core-developers/forge!864
2018-08-17 15:18:30 +00:00
Michael Kamensky
f951e831c0 Merge branch 'poisonousFix' into 'master'
Poisonous use :

See merge request core-developers/forge!865
2018-08-17 15:16:50 +00:00
Hanmac
226754d80f Poisonous use : 2018-08-17 15:31:33 +02:00
Hanmac
a72dfcacea Fortify use : 2018-08-17 15:19:09 +02:00
Hans Mackowiak
22766f9942 Merge branch 'moar_c18_cards' into 'master'
Add Windgrave's Judgmenet and Yennett (needs testing)

See merge request core-developers/forge!862
2018-08-17 12:30:23 +00:00
Hans Mackowiak
b927c790c7 Update TriggerDesc 2018-08-17 12:25:31 +00:00
tehdiplomat
1ae10cb5ca Fix Yennett draw 2018-08-17 07:55:14 -04:00
Jamin W. Collins
b19eca2aff only display the ante tab when playing for ante
Closes: core-developers/forge#381

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-16 23:48:13 -06:00
tehdiplomat
4a95685436 Add Windgrave's Judgmenet and Yennett 2018-08-16 21:48:51 -04:00
Jamin W. Collins
ab959890b8 fix a comment typo
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-16 17:32:45 -06:00
Sol
d2f5bd1a3d Update fatpacks.txt for M19 2018-08-15 15:19:43 +00:00
Sol
292ba47f70 Update boosterboxes.txt for M19 2018-08-15 15:18:35 +00:00
Sol
c026c399c3 Update screeching_phoenix.txt 2018-08-15 12:32:25 +00:00
Sol
c13aebe791 Update ajani_wise_counselor.txt 2018-08-15 12:31:35 +00:00
Michael Kamensky
f1f96557c2 Merge branch 'patch-1' into 'master'
Update loyal_subordinate.txt Trigger Zone Fix

See merge request core-developers/forge!858
2018-08-15 06:24:41 +00:00
Michael Kamensky
cdf9e3693d Merge branch 'patch-2' into 'master'
Update loyal_guardian.txt Trigger Zone Fix

See merge request core-developers/forge!859
2018-08-15 06:24:38 +00:00
Michael Kamensky
4cdc7b62ed Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_M195.

See merge request core-developers/forge!861
2018-08-15 06:05:02 +00:00
Agetian
5817a795f3 - Added puzzle PS_M195. 2018-08-15 08:38:15 +03:00
Michael Kamensky
0c5cab8d12 Merge branch 'c18_cards' into 'master'
More C18 cards

See merge request core-developers/forge!857
2018-08-15 05:01:40 +00:00
Computica
8c1f4c5720 Update loyal_guardian.txt Trigger Zone Fix 2018-08-15 04:20:14 +00:00
Computica
4ddfe40ff1 Update loyal_subordinate.txt Trigger Zone Fix 2018-08-15 04:19:00 +00:00
tehdiplomat
7be9ef06e0 Add Forge of Heroes 2018-08-14 17:37:53 -04:00
tehdiplomat
3dfa54e4da Add Saheeli, the Gifted 2018-08-14 17:10:04 -04:00
Sol
58c1bd35a9 Merge branch 'c18_cards' into 'master'
Boreas Charger, Endless Atlas, Ever-Watching Threshold

See merge request core-developers/forge!856
2018-08-14 20:22:02 +00:00
tehdiplomat
9c517070e4 Added Varchild 2018-08-14 16:18:50 -04:00
tehdiplomat
75caf0643a Added Tawnos 2018-08-14 16:02:25 -04:00
tehdiplomat
41ce31497b Add Tawnos Urza's Apprentice 2018-08-14 13:36:33 -04:00
tehdiplomat
fa1128b94c Magus of the Balance and Reality Scramble 2018-08-14 12:22:42 -04:00
tehdiplomat
e147900618 Move cards from upcoming to their appropriate letter 2018-08-14 09:04:40 -04:00
tehdiplomat
28c2aceba3 Boreas Charger, Endless Atlas, Ever-Watching Threshold 2018-08-14 08:35:38 -04:00
Sol
c45cf8df10 Merge branch 'network-play' into 'master'
Network play

See merge request core-developers/forge!854
2018-08-13 15:09:27 +00:00
Michael Kamensky
a0803cfce6 Merge branch 'rounded-corners' into 'master'
round image corners when No Border is selected

See merge request core-developers/forge!855
2018-08-13 06:16:36 +00:00
Jamin W. Collins
385fa74c6e round image corners when No Border is selected
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-12 23:53:59 -06:00
Jamin W. Collins
2aa8d4bb1a use CardFaceView for selecting CardFaces
needed for Runed Halo and similar

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-11 13:27:26 -06:00
Jamin W. Collins
269d6ead0c use CardView for Splice onto Arcane choice
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-11 13:24:40 -06:00
Jamin W. Collins
b980bbf66e adding check to ensure all args are Serializable
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-11 13:24:40 -06:00
Jamin W. Collins
e236202ddb ensure that charms use SpellAbilityView for choices
- needed for Funeral Charm

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-08-11 13:24:40 -06:00
Michael Kamensky
2174672511 Merge branch 'patch-1' into 'master'
Update yuriko_the_tigers_shadow.txt Ninjas You Control Fix

See merge request core-developers/forge!853
2018-08-11 12:04:19 +00:00
David Ellis
75ebadac10 Update yuriko_the_tigers_shadow.txt Ninjas You Control Fix 2018-08-11 10:49:43 +00:00
Michael Kamensky
2e994a4e89 Merge branch 'aftermathPlayEffect' into 'master'
Aftermath fix with PlayEffect

See merge request core-developers/forge!852
2018-08-11 10:07:30 +00:00
Hanmac
95568d2975 AbilityUtils: playEffect need to check the other restrictions
Aftermath: add extra check in SpellAbilityRestriction
2018-08-11 10:32:55 +02:00
Hans Mackowiak
e37a470127 Merge branch 'patch-jiang' into 'master'
Update jiang_yanggu.txt

See merge request core-developers/forge!851
2018-08-10 15:02:20 +00:00
Hans Mackowiak
8e80c8c5f5 Update jiang_yanggu.txt 2018-08-10 15:01:27 +00:00
Michael Kamensky
4e17dcc332 Merge branch 'cloneFix' into 'master'
GameAction: fix for Cloner leaving battlefield

See merge request core-developers/forge!850
2018-08-09 04:15:57 +00:00
Hanmac
7bf6aa7f6e add Estrid's Invocation using CorrectedSelf 2018-08-08 22:53:13 +02:00
Hanmac
36bf0344b0 GameAction: fix for Cloner leaving battlefield 2018-08-08 13:35:53 +02:00
Michael Kamensky
739d3abfe6 Merge branch 'mirari_conjecture_description_fix' into 'master'
Use the correct text for The Mirari Conjecture's third ability.

See merge request core-developers/forge!849
2018-08-08 03:00:01 +00:00
Michael Kamensky
2be7a22217 Merge branch 'iss644_thantis_fix' into 'master'
Thantis should cause all creatures to attack if able, not block if able.

See merge request core-developers/forge!848
2018-08-08 02:59:34 +00:00
kms70847
571029508f Use the correct text for The Mirari Conjecture's third ability. 2018-08-07 18:03:43 -04:00
kms70847
d1945e2c33 Thantis should cause all creatures to attack if able, not block if able.
Resolves the first third of https://git.cardforge.org/core-developers/forge/issues/644
2018-08-07 17:01:34 -04:00
Hans Mackowiak
eff8720500 Merge branch 'replicate_cost_display_fix' into 'master'
Resolves https://git.cardforge.org/core-developers/forge/issues/641.

Closes #641

See merge request core-developers/forge!842
2018-08-07 17:39:29 +00:00
Hans Mackowiak
780bae107b Merge branch 'master' into 'master'
Fixed Animate Dead and related cards.

Closes #643 and #620

See merge request core-developers/forge!847
2018-08-07 16:40:17 +00:00
Agetian
331d173a93 - Fixed Animate Dead.
- Fixed Dance of the Dead description.
- Added a couple related NPE prevention checks.
2018-08-07 19:21:39 +03:00
Michael Kamensky
1038dbbe0d Merge branch 'aminatousaugury' into 'master'
Fixed issues with Aminatou's Augury

See merge request core-developers/forge!845
2018-08-07 08:55:27 +00:00
austinio7116
7ac6bccf9f Fixed issues with Aminatou's Augury 2018-08-07 06:44:34 +01:00
Michael Kamensky
cb5222c997 Merge branch 'master' into 'master'
Fixed an issue with some cards still being shown incorrectly in the "choose a card name" dialog box.

See merge request core-developers/forge!844
2018-08-07 04:49:35 +00:00
Agetian
b91261bc06 - Fixed an issue with some cards still being shown an incorrect (transformed) image in a "choose a card name" dialog box in Desktop GUI (e.g. Treasure Map shown as Treasure Cove). 2018-08-07 07:47:52 +03:00
Michael Kamensky
49db080e06 Merge branch 'aminatousaugury' into 'master'
Aminatous Augury

See merge request core-developers/forge!841
2018-08-07 04:10:48 +00:00
Michael Kamensky
131c7d5a75 Merge branch 'puzzle-engine' into 'master'
Added puzzles PS_M193 and PS_M194.

See merge request core-developers/forge!843
2018-08-07 04:01:57 +00:00
Agetian
09bfe089de - Added puzzles PS_M193 and PS_M194. 2018-08-07 07:00:45 +03:00
kms70847
347707f74e Resolves https://git.cardforge.org/core-developers/forge/issues/641.
Updates the card file syntax for Replicate so it uses a colon, just like Kicker, Entwine, etc.
2018-08-06 21:43:18 -04:00
austinio7116
03d66b9ea1 Aminatous Augury 2018-08-06 19:35:18 +01:00
Michael Kamensky
a9ade92946 Merge branch 'master' into 'master'
M19 and C18 Planeswalker/Alt Wincon achievements by Marek.

See merge request core-developers/forge!840
2018-08-06 16:45:46 +00:00
Agetian
3c4b81f2ac - M19 alt wincon achievement (initial commit). 2018-08-06 19:40:27 +03:00
Agetian
5c0e70aa36 - C18 planeswalker achievements (initial commit). 2018-08-06 15:45:17 +03:00
Agetian
d5248e2d26 - M19 planeswalker achievements: initial commit. 2018-08-06 15:43:29 +03:00
Michael Kamensky
def2c3ed48 Merge branch 'tokenAttach' into 'master'
TokenEffect: Add AttachTo effect for Estrid

See merge request core-developers/forge!839
2018-08-06 12:20:07 +00:00
Hanmac
a98fdff570 Estrid the Masked: fixed Untap Effect 2018-08-06 13:25:09 +02:00
Hanmac
197880dda4 TokenEffect: refactor attachTo 2018-08-06 07:20:30 +02:00
Hanmac
08d984a699 TokenEffect: Add AttachTo effect for Estrid 2018-08-05 22:08:15 +02:00
Michael Kamensky
a56476645f Merge branch 'master' into 'master'
Update prismatic_lace.txt

See merge request core-developers/forge!838
2018-08-05 13:15:40 +00:00
Anthony Calosa
bb34d29a44 Update prismatic_lace.txt 2018-08-05 12:33:23 +00:00
Michael Kamensky
1dc2abb7a6 Merge branch 'updatedCommanderDeckgenData' into 'master'
Updated Commander card-based deck generation data to include recent sets and…

See merge request core-developers/forge!837
2018-08-05 06:39:44 +00:00
austinio7116
e59cd59ea2 Updated Commander card-based deck generation data to include recent sets and data from other sources. 2018-08-05 05:55:54 +01:00
Michael Kamensky
1d02087128 Merge branch 'morec18newcards' into 'master'
Nesting Dragon update to trigger names to avoid text changing effects with "dragon"

See merge request core-developers/forge!835
2018-08-05 03:27:15 +00:00
austinio7116
c106d856c0 Nesting Dragon update to trigger names to avoid text changing effects with "dragon" 2018-08-04 22:17:57 +01:00
Michael Kamensky
8d2668257c Merge branch 'morec18newcards' into 'master'
Tuvasa, the Sunlit and Nesting Dragon

See merge request core-developers/forge!830
2018-08-04 12:05:27 +00:00
Michael Kamensky
b0ce5a2a28 Merge branch 'fix_unzipped_path' into 'master'
Fix incorrect file path when extracting downloaded .zip

See merge request core-developers/forge!833
2018-08-04 11:57:03 +00:00
NikolayHD
7c32151d1f Fix incorrect file path when extracting downloaded .zip 2018-08-04 14:50:41 +03:00
Michael Kamensky
6a7584b3f2 Merge branch 'master' into 'master'
Fix compile after the previous revert.

See merge request core-developers/forge!832
2018-08-04 10:09:06 +00:00
Michael Kamensky
bef375650f - Fix compile after the previous revert. 2018-08-04 13:08:32 +03:00
Michael Kamensky
30fadb5912 Merge branch 'master' into 'master'
Reverted Paths.get in Zip downloader (breaks Android). Forge for Android 1.6.13.002.

See merge request core-developers/forge!831
2018-08-04 10:03:25 +00:00
Michael Kamensky
91ebc8549c - Preparing Forge for Android publish 1.6.13.002 [hotfix]. 2018-08-04 13:02:10 +03:00
Michael Kamensky
0a2dd16d83 - As much as I like Paths.get, it breaks Android. Reverting. 2018-08-04 13:01:37 +03:00
austinio7116
9815161bb0 Nesting Dragon (thanks inb63) 2018-08-04 10:39:46 +01:00
austinio7116
3d422076ad Tuvasa, the Sunlit 2018-08-04 10:34:22 +01:00
Michael Kamensky
ddb99c4cb5 Merge branch 'morec18newcards' into 'master'
Loyal Apprentice, Sower of Discord and updated Lieutenant filters

See merge request core-developers/forge!826
2018-08-04 05:51:49 +00:00
austinio7116
9127dc5d1e Fix for loyal unicorn's damage prevention 2018-08-04 06:36:12 +01:00
austinio7116
0876633990 Sower of Discord 2018-08-04 06:33:56 +01:00
Michael Kamensky
943e9836cd Merge branch 'quest_load_resilience' into 'master'
Improve resilience for Quest Loading

See merge request core-developers/forge!827
2018-08-04 04:50:30 +00:00
Michael Kamensky
b543f1b4d2 Merge branch 'master' into 'master'
Fixed a missing comment symbol in forge.profile.properties.example.

See merge request core-developers/forge!829
2018-08-04 04:47:53 +00:00
Agetian
600e8629f4 - Fixed a missing comment symbol in forge.profile.properties.example. 2018-08-04 07:47:26 +03:00
Michael Kamensky
308ecd0be8 Merge branch 'master' into 'master'
Preparing Forge for Android publish 1.6.13.001 [incremental].

See merge request core-developers/forge!828
2018-08-04 03:29:38 +00:00
Agetian
becd02c8e2 - Preparing Forge for Android publish 1.6.13.001 [incremental]. 2018-08-04 06:29:17 +03:00
tehdiplomat
be18d49de7 Fix Mobile project not catching new thrown error 2018-08-03 22:44:37 -04:00
tehdiplomat
ab77d97e59 Improve resilience for Quest Loading 2018-08-03 21:19:14 -04:00
Blacksmith
c05464585e Clear out release files in preparation for next release 2018-08-03 22:14:41 +00:00
Blacksmith
87c0ab7b7d [maven-release-plugin] prepare for next development iteration 2018-08-03 22:08:24 +00:00
Blacksmith
aea32aee62 [maven-release-plugin] prepare release forge-1.6.14 2018-08-03 22:08:21 +00:00
Blacksmith
6e8e28a615 Update README.txt for release 2018-08-03 22:06:33 +00:00
Blacksmith
9ca15ae635 Clear out release files in preparation for next release 2018-08-03 21:20:17 +00:00
Blacksmith
bb5102e311 [maven-release-plugin] prepare for next development iteration 2018-08-03 21:13:58 +00:00
Blacksmith
4d9b98bc94 [maven-release-plugin] prepare release forge-1.6.13 2018-08-03 21:13:55 +00:00
Blacksmith
1e1640b292 Update README.txt for release 2018-08-03 21:11:47 +00:00
austinio7116
1b47dce3e0 Consistent filter for Lieutenant cards to include Card.IsCommander+YouOwn+YouCtrl 2018-08-03 18:55:45 +01:00
austinio7116
6a340b9004 Loyal Apprentice 2018-08-03 07:25:47 +01:00
Michael Kamensky
e250077839 Merge branch 'fix_unzipped_path' into 'master'
Fix incorrect file path when extracting downloaded .zip

See merge request core-developers/forge!809
2018-08-03 04:05:27 +00:00
Michael Kamensky
5e4c7bce82 Merge branch 'master' into 'master'
- Fixed trigger description on Octopus Umbra.

See merge request core-developers/forge!825
2018-08-03 04:04:26 +00:00
Agetian
604010fbce - Fixed trigger description on Octopus Umbra. 2018-08-03 07:03:57 +03:00
Michael Kamensky
4b4bab6086 Merge branch 'morec18newcards' into 'master'
Nylea's Collossus, Octopus Umbra and Varina

See merge request core-developers/forge!823
2018-08-03 04:02:41 +00:00
Michael Kamensky
7c38b97540 Merge branch 'brudiclad3' into 'master'
Brudiclad, Telchor Engineer

See merge request core-developers/forge!822
2018-08-03 03:57:20 +00:00
austinio7116
1fa768bb81 Nylea's Colossus 2018-08-03 00:01:16 +01:00
austinio7116
eda2ba4adf A couple of scripts uploaded by Cryptix and inb63 2018-08-02 23:19:08 +01:00
Michael Kamensky
001a51a070 Merge branch 'master' into 'master'
Fix for Quest Reward Multiplier Issue

See merge request core-developers/forge!820
2018-08-02 03:50:49 +00:00
austinio7116
ac4d476ab3 Merge branch 'c18editions2' into 'master'
C18 scripts created by the ForgeScribe neural network that look correct on…

See merge request core-developers/forge!806
2018-08-01 22:12:09 +00:00
austinio7116
c41c3bd161 Brudiclad, Telchor Engineer
(cherry picked from commit 9b83f7b)
2018-08-01 22:03:01 +01:00
Wario
e2e0123d2b Update QuestWinLoseController.java 2018-08-01 20:37:58 +00:00
Michael Kamensky
7e0ebef57b Merge branch 'commanderNinjutsu' into 'master'
CardFactoryUtil: add Commander Ninjutsu variant

See merge request core-developers/forge!819
2018-08-01 16:47:50 +00:00
Michael Kamensky
4f53188b94 Merge branch 'master' into 'master'
Added a missing reference to Gyrus, Waker of Corpses.

See merge request core-developers/forge!821
2018-08-01 16:44:10 +00:00
Agetian
653cbd7345 - Added a missing reference to Gyrus, Waker of Corpses. 2018-08-01 19:43:21 +03:00
Michael Kamensky
3e980f30aa Merge branch 'c18fixedcards' into 'master'
Gyrus, Waker of Corpses and Retrofitter Foundry

See merge request core-developers/forge!817
2018-08-01 16:42:36 +00:00
Michael Kamensky
9acc59232b Merge branch 'fixlordwindgrace' into 'master'
Added cleanup to Lord Windgrace to fix first ability.

See merge request core-developers/forge!818
2018-08-01 16:24:27 +00:00
Wario
33d7596cac Update QuestWinLoseController.java 2018-08-01 12:25:11 +00:00
Wario
bded1d604a Fix for Quest Reward Multiplier Issue 2018-08-01 11:21:07 +00:00
Hanmac
fd1fe2b0e4 CardFactoryUtil: add Commander Ninjutsu variant 2018-08-01 07:58:59 +02:00
austinio7116
b5ccfb11e7 Added cleanup to Lord Windgrace to fix first ability.
(cherry picked from commit b286088)
2018-08-01 06:39:39 +01:00
austinio7116
d35af0a89d Merge branch 'c18planeswalkers' into 'master'
Lord Windgrace

See merge request core-developers/forge!813
2018-07-31 09:31:41 +00:00
austinio7116
2462ce58ab Gyrus, Waker of Corpses
(cherry picked from commit 8dcfe38)
2018-07-31 10:30:38 +01:00
austinio7116
1d57c1a444 Retrofitter Foundry
(cherry picked from commit 6c7161b)
2018-07-31 10:30:19 +01:00
austinio7116
3d55461332 Lord Windgrace - fixed oracle text brackets 2018-07-31 07:07:25 +01:00
austinio7116
cdb2474fa1 Merge branch 'etbCounterArixmethes' into 'master'
GameAction: fixed putEtbCounters with Arixmethes

See merge request core-developers/forge!814
2018-07-31 05:59:22 +00:00
Michael Kamensky
49e5cf7be0 Merge branch 'master' into 'master'
Forge for Android publish 1.6.12.001

See merge request core-developers/forge!816
2018-07-31 04:03:22 +00:00
Agetian
3b6ab7e8c1 - Preparing Forge for Android publish 1.6.12.001 [incremental]. 2018-07-31 07:02:02 +03:00
Blacksmith
0d15166137 [maven-release-plugin] prepare for next development iteration 2018-07-31 01:38:47 +00:00
Blacksmith
1d273d1c70 [maven-release-plugin] prepare release forge-1.6.12 2018-07-31 01:38:44 +00:00
Blacksmith
ba540a73ec Update README.txt for release 2018-07-31 01:36:50 +00:00
Mitchell Matthews
69de2ef5c5 Merge branch 'fix-suncleanser' into 'master'
Fix bad encoding for Suncleanser

See merge request core-developers/forge!815
2018-07-31 01:13:41 +00:00
tehdiplomat
b624832293 Fix bad encoding 2018-07-30 21:00:39 -04:00
Hanmac
d11bafd244 GameAction: fixed putEtbCounters with Arixmethes 2018-07-30 23:14:19 +02:00
NikolayHD
6256a157ab codestyle 2018-07-30 22:40:42 +03:00
NikolayHD
43a12342e9 Fix incorrect path when extracting downloaded .zip 2018-07-30 22:40:42 +03:00
austinio7116
3d85051f7f Merge branch 'arixmethes' into 'master'
Arixmethes, plus slumber counters

See merge request core-developers/forge!811
2018-07-30 15:57:29 +00:00
austinio7116
c83279ad1f Lord Windgrace 2018-07-30 10:18:44 +01:00
austinio7116
e65dcbe6d5 Arixmethes - enters tapped 2018-07-29 20:21:20 +01:00
austinio7116
5e16d52ce9 Arixmethes - enters tapped 2018-07-29 20:14:29 +01:00
austinio7116
8331660825 Arixmethes, plus slumber counters 2018-07-29 19:30:48 +01:00
Hans Mackowiak
fc3cd818b2 Merge branch 'patch-1' into 'master'
Update Kargan Dragonrider

See merge request core-developers/forge!808
2018-07-29 12:12:06 +00:00
Hans Mackowiak
19ca07e65d Update Kargan Dragonrider 2018-07-29 10:44:22 +00:00
austinio7116
e903cfc32c Fixed Whiptongue Hydra 2018-07-29 10:36:12 +01:00
Michael Kamensky
5be840ab1d Merge branch 'targetedOrControllerFix' into 'master'
cards: fixed cards with TargetedOrController

See merge request core-developers/forge!807
2018-07-29 08:56:23 +00:00
austinio7116
5567c32a4a Fixed Ravenous Slime 2018-07-29 09:54:47 +01:00
Michael Kamensky
d3db01cf69 - Added missing DBDamageResolve to Which of You Burns Brightest. 2018-07-29 11:38:08 +03:00
austinio7116
1379e06678 Correcting/polishing AI generated C18 scripts 2018-07-29 09:10:38 +01:00
Hanmac
e4c4a87d45 cards: fixed cards with TargetedOrController 2018-07-29 09:56:52 +02:00
austinio7116
8f10afac00 C18 scripts created by the ForgeScribe neural network that look correct on visual inspection and load in Forge without crashing
(cherry picked from commit b154229)
2018-07-29 07:33:33 +01:00
Michael Kamensky
8f8493bd47 Merge branch 'updateSparktongueDragon' into 'master'
Update Sparktongue Dragon to use Immediate Trigger Effect

See merge request core-developers/forge!805
2018-07-29 04:20:10 +00:00
tjtillmancoag
0d7081a119 Update Sparktongue Dragon to use Immediate Trigger Effect 2018-07-28 12:44:24 -07:00
Hans Mackowiak
2ae2f90f5b Merge branch 'patch-1' into 'master'
Update Commander 2018 remove SplitName

See merge request core-developers/forge!804
2018-07-28 05:11:30 +00:00
Hans Mackowiak
098790cbf3 Update Commander 2018 remove SplitName 2018-07-28 05:11:01 +00:00
Michael Kamensky
448f723c2c Merge branch 'iss633' into 'master'
Use distinct names for SVars

Closes #633

See merge request core-developers/forge!803
2018-07-28 03:17:33 +00:00
Michael Kamensky
924e5bdaf1 Merge branch 'c18editions2' into 'master'
C18 editions file generated by scryfall parsing script (still need to add type…

See merge request core-developers/forge!802
2018-07-28 03:16:03 +00:00
kms70847
da81748f54 Use distinct names for SVars
Possibly resolves https://git.cardforge.org/core-developers/forge/issues/633
2018-07-27 19:57:56 -04:00
austinio7116
422f8d2c80 C18 editions file generated by scryfall parsing script (still need to add type lookups for future sets)
(cherry picked from commit 97c3935)
2018-07-27 23:53:55 +01:00
Hans Mackowiak
157aa30918 Merge branch 'patch-1' into 'master'
Update sakashimas_student

See merge request core-developers/forge!801
2018-07-27 15:33:47 +00:00
Hans Mackowiak
ee87a75621 Update sakashimas_student 2018-07-27 15:33:15 +00:00
Michael Kamensky
6852748739 Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_M192.

See merge request core-developers/forge!800
2018-07-27 03:35:27 +00:00
Agetian
63bca8ce93 - Added puzzle PS_M192. 2018-07-27 06:34:44 +03:00
Sol
0d6fbb60ca Merge branch 'planeswalkerUpdate' into 'master'
Planeswalker update

See merge request core-developers/forge!662
2018-07-27 02:44:30 +00:00
Michael Kamensky
a670245b3d Merge branch 'bushidoFix' into 'master'
Bushido fix

See merge request core-developers/forge!794
2018-07-26 06:24:03 +00:00
Michael Kamensky
9b27742a5a Merge branch 'brawlm19format' into 'master'
Brawl M19 format update

See merge request core-developers/forge!799
2018-07-26 06:13:58 +00:00
austinio7116
ca7f551af4 Brawl M19 format update 2018-07-26 07:04:14 +01:00
Michael Kamensky
61040f4c05 Merge branch 'iss632' into 'master'
Swap order of operands so `.equals` can't get called on a null value.

Closes #632

See merge request core-developers/forge!797
2018-07-26 06:01:23 +00:00
Hanmac
421da7285c ComputerUtilCombat: hotfix for predictPowerBonusOfBlocker and predictToughnessBonusOfBlocker 2018-07-26 06:54:57 +02:00
Hanmac
6d829db99e ComputerUtilCombat: hotfix for predictPowerBonusOfAttacker and predictToughnessBonusOfAttacker 2018-07-26 06:48:33 +02:00
Hanmac
60d247503e cards: update Bushido 2018-07-26 06:35:07 +02:00
Hanmac
50cea02ae1 CardFactoryUtil: switch Bushido to new : format 2018-07-26 06:35:07 +02:00
kms70847
05c3402817 Swap order of operands so .equals can't get called on a null value.
Resolves https://git.cardforge.org/core-developers/forge/issues/632
2018-07-26 00:07:27 -04:00
Michael Kamensky
34c29be136 Merge branch 'keywordEnumTypeFix' into 'master'
Keyword: fixed getting Enum

See merge request core-developers/forge!795
2018-07-26 03:29:45 +00:00
Sol
efa8fa10d6 Update thorn_lieutenant.txt 2018-07-26 02:09:44 +00:00
Michael Kamensky
8f93f8e4bc Merge branch 'deckgenupdatem19' into 'master'
Updated deck gen data

See merge request core-developers/forge!796
2018-07-25 18:29:13 +00:00
austinio7116
fd061354ff Updated deck gen data 2018-07-25 18:43:14 +01:00
Agetian
b7553385f1 - Compile fix (deFails -> deTails) 2018-07-25 19:52:49 +03:00
Hanmac
805048b6b8 Keyword: fixed getting Enum 2018-07-25 17:57:12 +02:00
Michael Kamensky
85ad10a78a Merge branch 'immediate_trigger' into 'master'
Working on Immediate trigger type

See merge request core-developers/forge!752
2018-07-25 08:14:47 +00:00
Hans Mackowiak
2cc5bc11c9 Merge branch 'flashbackKeywordFix2' into 'master'
Flashback keyword fix2

See merge request core-developers/forge!793
2018-07-25 05:16:36 +00:00
Hanmac
88ac89a6d2 GameActionUtil: fix Flashback Cost 2018-07-24 20:05:14 +02:00
Hanmac
3bb9e96dd0 cards: update Flashback 2018-07-24 07:13:15 +02:00
Hanmac
36b62f2219 Keyword: replace "Flashback " with "Flashback:" 2018-07-24 07:08:32 +02:00
Sol
7a52668418 Merge branch 'fix_first_strike' into 'master'
Fix first strike

See merge request core-developers/forge!792
2018-07-24 02:51:22 +00:00
tehdiplomat
22efcf2f3f Fix first strike 2018-07-23 22:18:09 -04:00
Sol
1b95e2a23f Update ANNOUNCEMENTS.txt 2018-07-23 01:57:26 +00:00
Sol
2131bccae7 Update ISSUES.txt 2018-07-23 01:56:40 +00:00
tehdiplomat
5c90770e6f Remove TriggerPayCost. 2018-07-22 21:49:05 -04:00
tehdiplomat
3adf8a21c9 Add Immediate trigger types/Fix Always triggers
- Fix Skyrider Patrol
2018-07-22 21:49:05 -04:00
tehdiplomat
2c049b323a Working on Immediate trigger type 2018-07-22 21:45:40 -04:00
Michael Kamensky
c331a045cc Merge branch 'cloneEffectUpdate' into 'master'
Clone effect update

Closes #610

See merge request core-developers/forge!791
2018-07-22 09:57:58 +00:00
Hanmac
8a0446add1 Metamorphic Alteration: Choosing Creature is Part of RE 2018-07-22 08:05:34 +02:00
Hanmac
52190b9175 cards: update Clone effect with internal Choice 2018-07-22 08:05:34 +02:00
Hanmac
a484b5288e CloneEffect: add ChoiceZone and make the choice there mandatory 2018-07-22 08:05:34 +02:00
Michael Kamensky
483a01b936 Merge branch 'alwaysTriggerFix' into 'master'
GameAction: always trigger should not run if used preList

Closes #617

See merge request core-developers/forge!789
2018-07-21 17:59:22 +00:00
Hanmac
c21f2203c5 GameAction: always trigger should not run if used preList 2018-07-21 18:21:26 +02:00
Michael Kamensky
7d56940384 Merge branch 'brawl1v1update' into 'master'
Brawl 1v1 rules update

See merge request core-developers/forge!785
2018-07-21 05:26:10 +00:00
Michael Kamensky
c19832a559 Merge branch 'patch-7' into 'master'
Update cabal_stronghold.txt

See merge request core-developers/forge!777
2018-07-21 05:23:05 +00:00
Hans Mackowiak
07a0e08730 Merge branch 'iss628' into 'master'
Use `Creature.IsRemembered` instead of `Creature.Remembered` or `Targeted` when…

See merge request core-developers/forge!787
2018-07-21 05:06:27 +00:00
Hans Mackowiak
5329fb0b51 Merge branch 'iss628_blood_feud' into 'master'
Use `Player.IsRemembered`, not `Player.Remembered`.

See merge request core-developers/forge!788
2018-07-21 05:03:40 +00:00
kms70847
fc2b5f45a2 Use Player.IsRemembered, not Player.Remembered. 2018-07-21 00:31:46 -04:00
kms70847
86ab628cce Use Creature.IsRemembered instead of Creature.Remembered or Targeted when remembering the target of an ability that created a continuous effect 2018-07-20 23:20:47 -04:00
austinio7116
8db68d8717 1v1 brawl rule changes (Free mulligan) 2018-07-20 22:35:32 +01:00
austinio7116
40623420f9 1v1 brawl rule changes (25 starting life) 2018-07-20 21:56:02 +01:00
Michael Kamensky
d6be6f8b43 Merge branch 'patch-6' into 'master'
Update damping_sphere.txt

See merge request core-developers/forge!776
2018-07-20 04:54:44 +00:00
Michael Kamensky
434acedb8f Merge branch 'patch-3' into 'master'
Update rona_disciple_of_gix.txt

See merge request core-developers/forge!773
2018-07-20 04:54:33 +00:00
Michael Kamensky
165c95a5d8 Merge branch 'patch-4' into 'master'
Update skirk_prospector.txt

See merge request core-developers/forge!774
2018-07-20 04:54:06 +00:00
Michael Kamensky
04dc886c54 Merge branch 'patch-5' into 'master'
Update the_flame_of_keld.txt

See merge request core-developers/forge!775
2018-07-20 04:53:48 +00:00
Michael Kamensky
968407b169 Merge branch 'patch-8' into 'master'
Update whisper_blood_liturgist.txt

See merge request core-developers/forge!778
2018-07-20 04:52:28 +00:00
Michael Kamensky
8134d72861 Merge branch 'patch-13' into 'master'
Update goblin_grappler.txt

See merge request core-developers/forge!783
2018-07-20 04:52:20 +00:00
Michael Kamensky
69e5e4cb21 Merge branch 'iss618' into 'master'
Resolves https://git.cardforge.org/core-developers/forge/issues/618

Closes #618

See merge request core-developers/forge!784
2018-07-20 04:51:17 +00:00
Michael Kamensky
1319e0fda9 Merge branch 'patch-2' into 'master'
Allow Llanowar Envoy in AI draft decks

See merge request core-developers/forge!772
2018-07-20 04:50:25 +00:00
kms70847
674d09c06d Resolves https://git.cardforge.org/core-developers/forge/issues/618 2018-07-19 20:53:33 -04:00
Meerkov
f0833573be Update goblin_grappler.txt 2018-07-19 14:31:54 +00:00
Meerkov
a2d59cae6a Update whisper_blood_liturgist.txt 2018-07-19 07:12:08 +00:00
Meerkov
53eec599c4 Update cabal_stronghold.txt 2018-07-19 07:04:59 +00:00
Meerkov
e70fa161b4 Update damping_sphere.txt 2018-07-19 06:45:39 +00:00
Meerkov
a7a6a3265a Update the_flame_of_keld.txt 2018-07-19 06:32:59 +00:00
Meerkov
955c79b640 Update skirk_prospector.txt 2018-07-19 06:08:57 +00:00
Meerkov
253a4d389c Update rona_disciple_of_gix.txt 2018-07-19 05:14:06 +00:00
Meerkov
7d608ece67 Allow Llanowar Envoy in AI draft decks 2018-07-19 04:34:44 +00:00
Michael Kamensky
e572b4a506 Merge branch 'patch-1' into 'master'
Undo RemAiDeck on Deep Freeze

See merge request core-developers/forge!771
2018-07-19 04:11:33 +00:00
Meerkov
0b23d89783 There is no reason for AI not to play Deep Freeze. 2018-07-19 04:02:50 +00:00
Michael Kamensky
5eab3065bc Merge branch 'FixDepartedDeckhand' into 'master'
Departed Deckhand AI Targeting Fix

See merge request core-developers/forge!770
2018-07-18 17:06:52 +00:00
T.J. Tillman
5a5cfb30ef Add Svar so AI doesn't target its own Departed Deckhand with pump spells/auras. 2018-07-18 15:59:57 +00:00
Michael Kamensky
5b3c22201b Merge branch 'flashbackKeywordFix' into 'master'
Keyword: fix Flashback vs Flash

Closes #621

See merge request core-developers/forge!769
2018-07-18 06:31:15 +00:00
Hanmac
14ee2f99c8 Keyword: fix Flashback vs Flash 2018-07-18 07:45:48 +02:00
Michael Kamensky
a760901a79 Merge branch 'm19questimages' into 'master'
Quest images M19

See merge request core-developers/forge!768
2018-07-18 05:09:45 +00:00
austinio7116
2cf101ac7d Quest images M19 2018-07-17 20:42:00 +01:00
Michael Kamensky
48f0d5b5e4 Merge branch 'm19deckgenupdate' into 'master'
M19 deckgen updates for standard

See merge request core-developers/forge!767
2018-07-17 17:59:05 +00:00
austinio7116
eaaa18b15e M19 deckgen updates for standard 2018-07-17 18:18:14 +01:00
Michael Kamensky
613bae436d Merge branch 'puzzle-engine' into 'master'
GameState: preserve the number of lands played so that land drops from the previous game state do not affect the new game state

See merge request core-developers/forge!766
2018-07-17 15:27:18 +00:00
Agetian
3c2f5b2ecf - Also reset the lands played last turn. 2018-07-17 16:13:35 +03:00
Michael Kamensky
15aa393d19 Merge branch 'changes-updated' into 'master'
Updated changes before release.

See merge request core-developers/forge!765
2018-07-17 07:56:42 +00:00
Michael Kamensky
f8fda3fb2c Merge branch 'CorrectMacabreWaltz' into 'master'
Fix Macabre Waltz bug

See merge request core-developers/forge!763
2018-07-17 07:53:42 +00:00
Agetian
442263710b - GameState: preserve the number of lands played so that land drops from the previous game state do not affect the new game state. 2018-07-17 08:50:56 +03:00
maustin
41013330fc Updated changes before release. 2018-07-17 06:29:40 +01:00
Michael Kamensky
64b456827d Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_M191.

See merge request core-developers/forge!764
2018-07-17 05:25:35 +00:00
Agetian
54ad6393ac - Added puzzle PS_M191. 2018-07-17 08:24:53 +03:00
tjtillmancoag
c4a3e93e44 Fixed bug where if opponent controlled your creature and then it died, that creature couldn't be targeted by Macabre Waltz 2018-07-16 22:23:53 -07:00
Michael Kamensky
d51b9cfa24 Merge branch 'CorrectThornLieutenant' into 'master'
Fix Thorn Lieutenants token generating trigger

See merge request core-developers/forge!762
2018-07-17 05:09:31 +00:00
tjtillmancoag
7250fa7134 Changed parameter SourceType$ to ValidSource$ in Thorn Lieutenant so that it properly doesn't create tokens from being targeted by its controllers spells and abilities 2018-07-16 21:46:56 -07:00
Michael Kamensky
91b60f1c0f Merge branch 'cherry-pick-f5322112' into 'master'
Support viewing cardscripts from the "upcoming" folder in the Card Workshop.

Closes #18

See merge request core-developers/forge!761
2018-07-17 04:20:39 +00:00
schnautzr
3e87e8bb55 Support viewing cardscripts from the "upcoming" folder in the Card Workshop.
(cherry picked from commit f53221125cc5ecf218b145493838b89f43edc41c)
2018-07-17 02:36:41 +00:00
Michael Kamensky
dbe6f2714f Merge branch 'scriptFix' into 'master'
ForgeScript: try to fix the SpellAbility Checks

See merge request core-developers/forge!760
2018-07-16 17:58:29 +00:00
Hanmac
25220a46ee ForgeScript: try to fix the SpellAbility Checks 2018-07-16 19:51:54 +02:00
Michael Kamensky
ff3a0fffea Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_M190.

See merge request core-developers/forge!759
2018-07-15 18:24:11 +00:00
Agetian
c9f71ec6f2 - Added puzzle PS_M190. 2018-07-15 21:23:36 +03:00
Michael Kamensky
6bc8aabb6b Merge branch 'facedownTezz' into 'master'
ChangeZoneEffect: special Facedown rules for Tezzeret

See merge request core-developers/forge!758
2018-07-15 18:13:13 +00:00
Michael Kamensky
f577e5446a Merge branch 'CorrectSarkhanCost' into 'master'
Corrected Mana cost of Sarkhan, Dragonsoul

See merge request core-developers/forge!757
2018-07-15 17:48:22 +00:00
Hanmac
d18f891e8f ChangeZoneEffect: special Facedown rules for Tezzeret 2018-07-15 18:42:01 +02:00
tjtillmancoag
ca6514fa45 Corrected Mana cost of Sarkhan, Dragonsoul 2018-07-15 09:34:26 -07:00
Michael Kamensky
14043d88e5 Merge branch 'iss567' into 'master'
Remove set acronyms from more affected challenge decks

See merge request core-developers/forge!755
2018-07-15 04:49:10 +00:00
Michael Kamensky
73234133b1 Merge branch 'iss616' into 'master'
Add Battlefield TriggerZone to Barren Glory.

See merge request core-developers/forge!756
2018-07-15 04:48:48 +00:00
kms70847
5436aa10f0 Add Battlefield TriggerZone to Barren Glory.
Fix for https://git.cardforge.org/core-developers/forge/issues/616.
2018-07-14 17:37:16 -04:00
kms70847
87ffcbef1c Remove set acronyms from more affected challenge decks
Follow up to fix for https://git.cardforge.org/core-developers/forge/issues/567
2018-07-14 17:23:09 -04:00
Hans Mackowiak
4d297385a7 Merge branch 'm19remorsefulClericRemAIDeck' into 'master'
Remorseful Cleric fix

See merge request core-developers/forge!754
2018-07-14 21:09:29 +00:00
T.J. Tillman
6e5ebd50ba Update remorseful_cleric.txt 2018-07-14 20:56:22 +00:00
Hans Mackowiak
9020530a74 Merge branch 'master' into 'master'
Don't treat a copied ability's host like it's a copy of a card

Closes #567

See merge request core-developers/forge!753
2018-07-14 20:36:57 +00:00
kms70847
44f19b13a7 Merge remote-tracking branch 'upstream/master' 2018-07-14 15:07:55 -04:00
T.J. Tillman
a30aa5d873 Update remorseful_cleric.txt 2018-07-14 16:56:02 +00:00
tjtillmancoag
4d611cc0b9 For now AI always immediately sacrifices Remorseful Cleric, so removing it from AI decks 2018-07-14 00:09:53 -07:00
Michael Kamensky
918f0844eb Merge branch 'cherry-pick-33f53390' into 'master'
add m19 decks

See merge request core-developers/forge!746
2018-07-14 04:18:09 +00:00
Michael Kamensky
4f56595003 Merge branch 'm19someMoreFixes' into 'master'
Few M19 Fixes

See merge request core-developers/forge!748
2018-07-14 04:17:16 +00:00
kms70847
42dcd45392 Merge branch 'master' of git.cardforge.org:magicalblender/forge 2018-07-13 20:09:51 -04:00
kms70847
f43cc09b6f Don't specify block acronym when defining HumanExtras or AIExtras.
Resolves https://git.cardforge.org/core-developers/forge/issues/567
2018-07-13 20:07:10 -04:00
kms70847
8f4eb8596d Don't treat a copied ability's host like it's a copy of a card
Bugfix follow up to commit that solved https://git.cardforge.org/core-developers/forge/issues/615
2018-07-13 18:29:03 -04:00
kms70847
5459914156 Don't treat a copied ability's host like it's a copy of a card
Bugfix follow up to commit that solved https://git.cardforge.org/core-developers/forge/issues/615
2018-07-13 18:20:58 -04:00
Rob Schnautz
ecce77bbc6 Merge branch 'master' into 'cherry-pick-33f53390'
update branch

See merge request core-developers/forge!751
2018-07-13 21:43:18 +00:00
Rob Schnautz
dccfd3ce08 Add shop and metadata properties 2018-07-13 21:38:27 +00:00
Hans Mackowiak
c29457d341 Merge branch 'CounterNumFix' into 'master'
CounterPut: fix with CounterNum

See merge request core-developers/forge!750
2018-07-13 17:38:21 +00:00
Hanmac
dfdee167a4 CounterPut: fix with CounterNum 2018-07-13 19:37:00 +02:00
Michael Kamensky
ab17ab70c5 Merge branch 'schnautzr-master-patch-39605' into 'master'
M19 now legal in Limited formats

See merge request core-developers/forge!749
2018-07-13 16:57:35 +00:00
T.J. Tillman
e79b2a3ad3 Update militia_bugler.txt 2018-07-13 16:56:31 +00:00
Rob Schnautz
53c50afca0 M19 now legal in Limited formats 2018-07-13 14:12:37 +00:00
tjtillmancoag
43862557f4 Few M19 Fixes
-Ajani, Adversary of Tyrants's first ability didn't specify NumCounters and was causing it to crash
-Fixed capitalization of nontoken to nonToken in Lena, Selfless Champion
-Militia Bugler was revealing all 4 cards dug to both players, fixed that
-Open the Graves was naming the zombies "Saproling", fixed to "Zombie"
-Made Skyrider Patrol work at least the way it was scripted (still isn't technically correct, but it works now)
-Corrected cost of Vivien Reid's ultimate
2018-07-12 22:18:06 -07:00
Hans Mackowiak
204da34651 Merge branch 'master' into 'master'
When copying abilities, the source of the ability should not be copied.

Closes #615

See merge request core-developers/forge!747
2018-07-13 05:02:18 +00:00
kms70847
f92fd358c8 When copying abilities, the source of the ability should not be copied.
Resolves https://git.cardforge.org/core-developers/forge/issues/615
2018-07-12 23:52:34 -04:00
Michael Kamensky
407b8b213c Merge branch 'patch-1' into 'master'
normalize filename

See merge request core-developers/forge!745
2018-07-13 03:18:29 +00:00
schnautzr
9a14dba0f8 add m19 decks
(cherry picked from commit 33f533900d2a49236ee551e2524700487869c342)
2018-07-13 03:06:45 +00:00
Rob Schnautz
94e4b00226 normalize filename 2018-07-12 20:54:54 +00:00
Michael Kamensky
19aaf067d8 Merge branch 'master' into 'master'
Added an alt token image spec for Jiang Yanggu's Mowu token.

See merge request core-developers/forge!744
2018-07-08 05:40:40 +00:00
Agetian
9de2a69c26 - Added an alt token image spec for Jiang Yanggu's Mowu token. 2018-07-08 08:39:03 +03:00
Michael Kamensky
059644a27d Merge branch 'charm-ai' into 'master'
Fixed AI not making choices for AF Charm when playing spells without paying mana cost

See merge request core-developers/forge!742
2018-07-07 13:32:00 +00:00
Michael Kamensky
ac12db1738 Merge branch 'eggs' into 'master'
cards: Egg now is a creature type again

See merge request core-developers/forge!741
2018-07-07 13:31:51 +00:00
Michael Kamensky
bcf4485331 Merge branch 'master' into 'master'
Fixed Fell Specter.

See merge request core-developers/forge!743
2018-07-07 13:31:20 +00:00
Michael Kamensky
8493f9ca2c - Fixed Fell Specter. 2018-07-07 16:30:47 +03:00
Michael Kamensky
e89a742b24 - Added a simple CheckDFC logic for putting counters on cards that can transform later as a result (Ludevic's Test Subject). 2018-07-07 14:43:17 +03:00
Michael Kamensky
020e02fb50 - AI should make choices for AF Charm when playing a SA with this API without paying its mana cost. 2018-07-07 14:21:05 +03:00
Hanmac
5bbd10dfbb cards: Egg now is a creature type again 2018-07-07 10:26:49 +02:00
Michael Kamensky
eb102da367 Merge branch 'fixDruidofHorns' into 'master'
Few M19 fixes

See merge request core-developers/forge!739
2018-07-07 04:46:36 +00:00
Michael Kamensky
2dd7d8d9ba Merge branch 'm19finalfew' into 'master'
Fixed Shield Mare triggering on wrong sources

See merge request core-developers/forge!738
2018-07-07 04:46:01 +00:00
tjtillmancoag
f6690b2bee Corrected Druid of Horns to be a 2/3 2018-07-06 16:26:50 -07:00
tjtillmancoag
5f0bd1852d Few M19 fixes
-Corrected Draconic Disciple to a 2/2
-Fixed Skilled Reanimator so that targeted non-creature artifacts will become creatures
2018-07-06 16:24:31 -07:00
austinio7116
ff592f0bd3 Fixed Shield Mare triggering on wrong sources 2018-07-06 20:28:28 +01:00
Michael Kamensky
078a713302 Merge branch 'master' into 'master'
Move Skyrider Patrol out of upcoming.

See merge request core-developers/forge!737
2018-07-06 12:13:03 +00:00
Agetian
8fa6f80acd - Move Skyrider Patrol out of upcoming. 2018-07-06 15:12:37 +03:00
Michael Kamensky
a0576418aa Merge branch 'm19-aiflags' into 'master'
Adding AI deckbuilding and some gameplay hints for M19.

See merge request core-developers/forge!736
2018-07-06 11:52:01 +00:00
Agetian
b1b892bbc8 - Adding AI deckbuilding and some gameplay hints for M19. 2018-07-06 14:50:13 +03:00
Michael Kamensky
24fbcd2859 Merge branch 'master' into 'master'
Fixed Brawl-Bash Ogre implementation

See merge request core-developers/forge!735
2018-07-06 11:25:29 +00:00
Agetian
7d421a482d - Fixed Brawl-Bash Ogre (CARDNAME). 2018-07-06 14:08:30 +03:00
Agetian
14438ac5b3 - Fixed Brawl-Bash Ogre. 2018-07-06 14:02:56 +03:00
Michael Kamensky
93fb466075 Merge branch 'master' into 'master'
Fixed description on Liliana, Untouched by Death ultimate.

See merge request core-developers/forge!734
2018-07-06 10:28:59 +00:00
Agetian
0f5ad7e851 - Fixed description on Liliana, Untouched by Death ultimate. 2018-07-06 13:28:30 +03:00
Michael Kamensky
a61adf78b6 Merge branch 'master' into 'master'
Fixed cost on Liliana, Untouched by Death ultimate.

See merge request core-developers/forge!733
2018-07-06 10:23:06 +00:00
Agetian
a1f8ee4a0e - Fixed cost on Liliana, Untouched by Death ultimate. 2018-07-06 13:22:46 +03:00
Michael Kamensky
9ed0f74820 Merge branch 'm19finalfew' into 'master'
Fix Liliana's last ability

See merge request core-developers/forge!732
2018-07-06 10:18:24 +00:00
austinio7116
b124d8b4c4 Fix Liliana's last ability 2018-07-06 10:20:31 +01:00
Michael Kamensky
7b40392bc5 Merge branch 'setStateFixes' into 'master'
SetState: nonPerm can't turn faceup, also canTransform check

See merge request core-developers/forge!728
2018-07-06 06:19:20 +00:00
Michael Kamensky
3138c7b3c4 Merge branch 'effectOnlyPirDoubling' into 'master'
ReplaceEffect: make Pir work with Doubling Season

Closes #609

See merge request core-developers/forge!730
2018-07-06 05:56:12 +00:00
Michael Kamensky
84e854dc39 Merge branch 'master' into 'master'
Fix up and merge M19 patches by T.J.Tillman

See merge request core-developers/forge!731
2018-07-06 05:53:30 +00:00
Agetian
40c592aad4 - Fixed Demanding Dragon.
- Removed an empty line in Abnormal Endurance.
2018-07-06 08:51:09 +03:00
Hanmac
f8b0d027d7 ReplaceEffect: make Pir work with Doubling Season 2018-07-06 07:24:56 +02:00
Hanmac
3d90acdfa4 SetStateAi: cleanup 2018-07-06 07:11:50 +02:00
tjtillmancoag
850850bc29 Several fixes for M19 cards
-A random one of the ten Dual taplands will show up in basic land slot 5 out of 12 times and will no longer occure in the common slot
-Abnormal Endurance mana cost and functionality fixed
-Demanding Dragon - CPU would always choose to take the damage instead of sac a creature even if lethal. Reversed the trigger to sac a creature "unless" they take the damage, which seems to fix the AI taking lethal damage
-Herald of Faith - P/T fixed
-Heroic Reinforcements - Mana cost fixed
-Patient Rebuilding - Was drawing a card for every land ever milled by it, rather than just out of the 3 this turn, fixed that.
-Plague Mare - Added P/T
-Rhox Oracel - Added "Creature" to type
-Vampire Sovereign - Corrected it's trigger to target opponent rather than target player
2018-07-05 21:39:06 -07:00
Hanmac
d2e02453d8 SetState: nonPerm can't turn faceup, also canTransform check 2018-07-05 22:50:37 +02:00
Michael Kamensky
7675fec424 Merge branch 'm19finalfew' into 'master'
Fixed a few more M19 script errors

See merge request core-developers/forge!727
2018-07-05 18:15:38 +00:00
austinio7116
db6737b157 Added missing Fell Spectre P/T 2018-07-05 16:58:20 +01:00
austinio7116
90897eccd3 Fix triggerzones on Sai 2018-07-05 16:06:57 +01:00
austinio7116
2dca199f32 Merge branch 'master' into 'master'
Fixed Ajani's Last Stand (AI crash).

Closes #608

See merge request core-developers/forge!726
2018-07-05 15:02:50 +00:00
Michael Kamensky
e63ea63cc5 - Added trigger zone spec (2). 2018-07-05 15:53:18 +03:00
Michael Kamensky
cad0fa589b - Added trigger zone spec. 2018-07-05 14:14:22 +03:00
Michael Kamensky
2072d960c8 - Better solution for Ajani's Last Stand. 2018-07-05 14:12:34 +03:00
Michael Kamensky
2d631fce66 - Fixed Ajani's Last Stand. 2018-07-05 13:38:16 +03:00
Sol
cddcce8cbf Merge branch 'm19finalfew' into 'master'
Corrected Vampire Sovereign ChangeZone to only trigger on self

See merge request core-developers/forge!725
2018-07-04 23:57:42 +00:00
austinio7116
d39df3d883 Corrected Vampire Sovereign ChangeZone to only trigger on self 2018-07-04 22:31:27 +01:00
Michael Kamensky
90e5806813 Merge branch 'm19finalfew' into 'master'
Fixed Dwindle destroy trigger

See merge request core-developers/forge!724
2018-07-04 17:47:43 +00:00
austinio7116
de458b2779 Fixed Dwindle destroy trigger 2018-07-04 18:31:10 +01:00
Michael Kamensky
ff6486ae59 Merge branch 'master' into 'master'
Fixed Lich's Caress.

See merge request core-developers/forge!723
2018-07-04 08:21:38 +00:00
Michael Kamensky
20607547df - Fixed Lich's Caress. 2018-07-04 11:20:57 +03:00
Michael Kamensky
661f297bd5 Merge branch 'm19finalfew' into 'master'
Added final missing M19 cards - corrected the name of Isareth the Awakener

See merge request core-developers/forge!722
2018-07-04 08:19:19 +00:00
austinio7116
90ac2f56d8 Fixed doublecast cost 2018-07-04 07:02:39 +01:00
austinio7116
3eb4b933a7 Tezzeret's Strider 2018-07-04 07:02:09 +01:00
austinio7116
d5666fcbd0 Silverbeak Griffin 2018-07-04 06:58:12 +01:00
austinio7116
9561e71299 Court Cleric 2018-07-04 06:56:25 +01:00
austinio7116
55f4d705b0 Corrected the name of Isareth the Awakener 2018-07-04 06:36:30 +01:00
Hanmac
246e3e2bef updated Bolas flip walker 2018-07-04 07:22:55 +02:00
Hanmac
2ba61df85b cards: update flip walker 2018-07-04 07:21:21 +02:00
Hanmac
30be1aab32 Planeswalker: get BaseLoyalty from state, remove damage redirect 2018-07-04 07:21:21 +02:00
Michael Kamensky
865dbbf17a Merge branch 'm19_missing_red' into 'master'
Add Vaevictis Asmadi, the Dire

See merge request core-developers/forge!720
2018-07-04 04:39:12 +00:00
Michael Kamensky
fa5c799905 Merge branch 'master' into 'master'
Fixed Stitcher's Supplier crashing the game, removed unfinished Tezzeret, Cruel Machinist (in separate WIP MR, crashes the game upstream)

See merge request core-developers/forge!721
2018-07-04 04:33:59 +00:00
Agetian
dc094bad11 - Fixed Stitcher's Supplier crashing the game.
- Removed Tezzeret, Cruel Machinist (no code support for EnterFaceDownAs upstream yet, in separate WIP MR)
2018-07-04 07:32:53 +03:00
Sol
0cc7d20b0b Merge branch 'm19fixes2' into 'master'
Removed UST card added by mistake

See merge request core-developers/forge!719
2018-07-04 02:53:47 +00:00
Sol
527e775952 Merge branch 'finalpromos' into 'master'
Final M19 black planeswalker deck cards

See merge request core-developers/forge!718
2018-07-04 02:53:32 +00:00
Sol
43dc22c766 Merge branch 'finalm19cards' into 'master'
PW Deck Cards plus Cryptix Black scripts

See merge request core-developers/forge!711
2018-07-04 02:53:08 +00:00
Chris H
92dca74763 Add Vaevictis Asmadi, the Dire 2018-07-03 22:44:54 -04:00
austinio7116
9738d0e549 Removed UST card added by mistake 2018-07-04 00:15:23 +01:00
maustin
3ed6695f28 Merge branch 'master' into finalm19cards
# Conflicts:
#	forge-gui/res/cardsfolder/s/sarkhans_dragonfire.txt
2018-07-03 22:14:56 +01:00
austinio7116
bbbb10e846 Merge branch 'schnautzcards' into 'master'
Added Schnautzr's promo cards from M19

See merge request core-developers/forge!717
2018-07-03 19:51:46 +00:00
austinio7116
6586d30961 Liliana, the Necromancer 2018-07-03 20:49:19 +01:00
austinio7116
4a86179c1b Arisen Gorgon 2018-07-03 20:26:37 +01:00
austinio7116
dbae78d2f9 Gravewaker 2018-07-03 20:16:38 +01:00
schnautzr
341bdaaf44 Added Schnautzr's promo cards from M19 2018-07-03 19:57:35 +01:00
austinio7116
0f36ae74ad Fixed Isareth the awakener name 2018-07-03 19:36:25 +01:00
austinio7116
e3abd8fb01 Corrected spelling in Demon of Catastrophe's Type 2018-07-03 19:29:02 +01:00
Michael Kamensky
bba0ba7f19 Merge branch 'master' into 'master'
Fixed Leonin Warleader trigger description.

See merge request core-developers/forge!716
2018-07-03 17:51:36 +00:00
Agetian
3a7efd879f - Fixed Leonin Warleader trigger description. 2018-07-03 20:50:38 +03:00
Michael Kamensky
33e3fe12e8 Merge branch 'puzzle-engine' into 'master'
PS_KTKT: Added set defs to basic lands.

See merge request core-developers/forge!715
2018-07-03 16:55:37 +00:00
Agetian
f12c185b18 - PS_KTKT: Added set defs to basic lands. 2018-07-03 19:55:12 +03:00
Michael Kamensky
ca3b717395 Merge branch 'puzzle-engine' into 'master'
Added puzzle PS_KTKT (Khans of Tarkir throwback puzzle).

See merge request core-developers/forge!714
2018-07-03 16:46:09 +00:00
Agetian
b3a277a734 - Added puzzle PS_KTKT (Khans of Tarkir throwback puzzle). 2018-07-03 19:45:05 +03:00
austinio7116
5b684e42e6 Fell Spectre TriggeredCardController fix 2018-07-03 16:56:22 +01:00
Michael Kamensky
e7609f6e43 Merge branch 'master' into 'master'
Fix up the red cards and merge them

See merge request core-developers/forge!713
2018-07-03 15:00:19 +00:00
Michael Kamensky
0a782b24a6 - Fixed Leonin Warleader and Sarkhan's Unsealing. 2018-07-03 17:59:20 +03:00
Chris H
4ccc28a117 Add Sarkhan's Uunsealing 2018-07-03 10:36:07 -04:00
Chris H
73d438de27 Added 4 more M19 cards 2018-07-03 09:43:56 -04:00
austinio7116
9f819527f5 Added Sarkhan Dragonsoul 2018-07-03 08:22:20 +01:00
austinio7116
d584f82516 Fixed exception generating issues in black card scripts 2018-07-03 08:20:18 +01:00
austinio7116
bf5cda35e4 Bone Dragon Fixes 2018-07-03 08:07:40 +01:00
austinio7116
2ee8dd1e37 Added missing oracle text and card type.
Fixed a couple of text errors pre-testing
2018-07-03 07:17:12 +01:00
austinio7116
cea0ffd382 Black Cards from Cryptix 2018-07-03 06:52:21 +01:00
austinio7116
2e7e4299e7 Kargan Dragonrider 2018-07-03 06:41:43 +01:00
austinio7116
a515ab8bdf Sarkhan's Dragonfire 2018-07-03 06:29:31 +01:00
Michael Kamensky
dd9e41f532 Merge branch 'master' into 'master'
Migrate the latest card scripts.

See merge request core-developers/forge!710
2018-07-03 05:14:00 +00:00
Agetian
9a01efe126 - Migrate the latest card scripts. 2018-07-03 08:13:10 +03:00
Michael Kamensky
29fa7f6e7b Merge branch 'pwm19cards' into 'master'
More Planeswalker deck scripts Green/Red

See merge request core-developers/forge!708
2018-07-03 04:47:38 +00:00
Sol
de5fb29e04 Merge branch 'migrate_m19_upcoming' into 'master'
Migrate M19 cards

See merge request core-developers/forge!709
2018-07-03 01:35:27 +00:00
Chris H
119f0e464f Migrate M19 cards 2018-07-02 21:21:20 -04:00
austinio7116
b173ae4dc3 Sarkhan's Whelp 2018-07-02 17:27:08 +01:00
austinio7116
e94afc8a39 Vivien of the Arkbow 2018-07-02 17:08:48 +01:00
austinio7116
8946523053 Aggressive Mammoth 2018-07-02 16:52:10 +01:00
Michael Kamensky
a8217cbe40 Merge branch 'muFix' into 'master'
Update mu_yanling.txt

See merge request core-developers/forge!707
2018-07-02 09:12:07 +00:00
Michael Kamensky
a1c972c4c6 Merge branch 'pwm19cards' into 'master'
M19 Planeswalker Deck Cards 1

See merge request core-developers/forge!706
2018-07-02 09:12:03 +00:00
Rob Schnautz
18401c4fc3 Update mu_yanling.txt 2018-07-02 10:40:17 +02:00
austinio7116
f86e2fd957 Ursine Champion 2018-07-02 06:49:25 +01:00
austinio7116
5c7fa18e4d Vivien's Jaguar 2018-07-02 06:39:29 +01:00
Michael Kamensky
1db58cb862 Merge branch 'master' into 'master'
Master

See merge request core-developers/forge!704
2018-07-01 08:48:47 +00:00
Michael Kamensky
7dacfb64aa - Description fix for Palladia-Mors, the Ruiner. 2018-07-01 11:46:58 +03:00
Michael Kamensky
f6dc3cc324 Merge branch 'm19othercards' of git.cardforge.org:Austin/forge into agetian-master 2018-07-01 11:43:20 +03:00
austinio7116
f62cd7649b Palladia Mors 2018-07-01 07:17:19 +01:00
Michael Kamensky
fd581083ec Merge branch 'm19fixes' into 'master'
Fix trigger valid target of desecrated tomb

See merge request core-developers/forge!702
2018-07-01 06:10:42 +00:00
austinio7116
7d330e5804 Fix trigger valid target of desecrated tomb 2018-07-01 05:44:23 +00:00
Michael Kamensky
e72a8f7c03 Merge branch 'm19fixes' into 'master'
M19fixes

See merge request core-developers/forge!701
2018-07-01 05:43:35 +00:00
austinio7116
94cdb89f5c Fix desecrated tomb triggerzones 2018-07-01 06:30:21 +01:00
austinio7116
059e9a6844 Typo in amulet of safekeeping in editions file
(cherry picked from commit 5eb40c3)
2018-07-01 06:28:50 +01:00
Michael Kamensky
dc412bd1b7 Merge branch 'assorted-fixes' into 'master'
Merge fixes for Nicol Bolas, the Ravager part 2.

See merge request core-developers/forge!700
2018-07-01 04:26:57 +00:00
Agetian
9d53ee1094 - Merge fixes for Nicol Bolas, the Ravager. 2018-07-01 07:25:38 +03:00
Michael Kamensky
a8977eebcc Merge branch 'assorted-fixes' into 'master'
Nicol Bolas, the Ravager: merge fixes

See merge request core-developers/forge!699
2018-07-01 04:24:37 +00:00
Agetian
060bd0261f - Merge fixes for Nicol Bolas, the Ravager. 2018-07-01 07:23:51 +03:00
austinio7116
989404d4f0 Fixed bolas second ability to correct targets 2018-06-30 19:22:40 +01:00
austinio7116
da23cbf28d Corrected name of bolas in the editions file 2018-06-30 19:02:27 +01:00
austinio7116
43aba315e8 Added planeswalker$ true to +2 ability 2018-06-30 18:59:12 +01:00
Michael Kamensky
7ee51141d6 Merge branch 'autumn_veil' into 'master'
CantBeCounteredBy : make Autumns veil card text changing work

See merge request core-developers/forge!697
2018-06-30 17:12:58 +00:00
Agetian
b215bc6a39 - Autumn's Veil: Make sure the SVar name for effect ability does not contain color names in it so that it's not replaced (which leads to a crash). 2018-06-30 20:03:11 +03:00
Hanmac
ab32251643 ForgeScript: add missing connector to HostCard 2018-06-30 17:50:50 +02:00
Hanmac
c37701ef1e CantBeCounteredBy : make Autumns veil card text changing work 2018-06-30 17:45:04 +02:00
Michael Kamensky
7c97129dcf Merge branch 'NicolBolas' into 'master'
Nicol bolas

See merge request core-developers/forge!696
2018-06-30 13:54:57 +00:00
Michael Kamensky
e510f1de50 - Fixed Nicol Bolas, the Ravager. 2018-06-30 16:53:33 +03:00
austinio7116
0394e4dff7 Nicol Bolas, the Ravager - added InTargetedLibrary for Xcounts 2018-06-30 11:53:01 +01:00
Michael Kamensky
419cfc1f39 Merge branch 'm19' into 'master'
Add green cards

See merge request core-developers/forge!694
2018-06-29 18:15:28 +00:00
Agetian
6b29ad054b - A couple minor fixes. 2018-06-29 21:02:57 +03:00
Michael Kamensky
1456639ba9 Merge branch 'm19draft' into 'master'
M19draft

See merge request core-developers/forge!693
2018-06-29 17:11:02 +00:00
Rob Schnautz
4b9586ad8d Merge branch 'master' into 'm19' 2018-06-29 15:40:15 +00:00
schnautzr
1db51b7cfb Add green cards 2018-06-29 10:36:57 -05:00
maustin
5742c8eb23 Merge remote-tracking branch 'Austin/m19draft' into m19draft
# Conflicts:
#	forge-gui/res/draft/rankings.txt
2018-06-29 15:36:26 +01:00
austinio7116
71778ea3d8 Initial draft rankings for M19 2018-06-29 15:35:09 +01:00
austinio7116
dbbf70cd35 Initial draft rankings for M19 2018-06-29 09:24:48 +01:00
Michael Kamensky
f64293c92d Merge branch 'master' into 'master'
Minor update: added a break statement to GuiChoose.

See merge request core-developers/forge!692
2018-06-29 05:56:17 +00:00
Agetian
fe49c460eb - Added a break statement to GuiChoose. 2018-06-29 08:55:41 +03:00
Michael Kamensky
689d9af022 Merge branch 'changeZoneCauseTrigger' into 'master'
TriggerChangesZone: add ValidCause parameter

See merge request core-developers/forge!691
2018-06-29 05:54:02 +00:00
Hans Mackowiak
9a0c6b2ed0 Merge branch 'cardFacePredicate' into 'master'
CardFacePredicate: use new valid Predicate

See merge request core-developers/forge!690
2018-06-29 05:43:39 +00:00
Agetian
046f1019f5 - Vivien's Invocation: force reveal to controller (for "Look at the top seven cards..."). 2018-06-29 08:43:14 +03:00
Hanmac
543450d859 TriggerChangesZone: add ValidCause parameter 2018-06-29 07:15:28 +02:00
Agetian
eafc717dd8 - GuiChoose (desktop Forge): show the right card face in the card detail panel when choosing a card name. 2018-06-29 08:08:03 +03:00
Michael Kamensky
7b64fa487b Merge branch 'm19' into 'master'
Add red cards

See merge request core-developers/forge!689
2018-06-29 03:39:23 +00:00
Hanmac
d890ee248c CardFacePredicate: use new valid Predicate 2018-06-28 22:40:16 +02:00
schnautzr
d3d84db23d Add red cards 2018-06-28 08:53:16 -05:00
Michael Kamensky
db9114986f Merge branch 'm19scripts_5' into 'master'
M19scripts Chromium and Arcades Elder Dragons

See merge request core-developers/forge!688
2018-06-28 08:41:15 +00:00
austinio7116
815150de4e Update chromium_the_mutable.txt - removed permanent flag 2018-06-28 07:55:25 +00:00
austinio7116
1d797ebe80 M19: Chromium, the Mutable 2018-06-27 21:53:02 +01:00
austinio7116
e65f8e490e M19: Arcades, the Strategist 2018-06-27 21:39:20 +01:00
Michael Kamensky
e91a82c775 Merge branch 'm19_multicolourscripts' into 'master'
M19 multicolourscripts part 1

See merge request core-developers/forge!687
2018-06-27 09:56:28 +00:00
Michael Kamensky
ef751a2e11 Merge branch 'm19' into 'master'
M19 cards

See merge request core-developers/forge!686
2018-06-27 09:53:53 +00:00
Michael Kamensky
9ce48be89b Merge branch 'm19_scripts_4' into 'master'
M19 scripts - remaining artifacts

See merge request core-developers/forge!684
2018-06-27 09:53:27 +00:00
austinio7116
a0b092c544 M19: Aerial Engineer 2018-06-27 08:04:26 +01:00
austinio7116
dce0cfafae M19: Brawl-Bash Ogre 2018-06-27 07:57:50 +01:00
austinio7116
79dd082e00 M19: Draconic Disciple 2018-06-27 07:40:05 +01:00
austinio7116
9cc764188f M19: Heroic Reinforcements 2018-06-27 06:54:18 +01:00
austinio7116
63874041b4 M19: Poison-tip Archer 2018-06-27 06:33:54 +01:00
Michael Kamensky
bfffb5114c Merge branch 'metamorphic-alteration' into 'master'
Metamorphic alteration

See merge request core-developers/forge!685
2018-06-27 05:00:44 +00:00
austinio7116
f8de4ffc2d M19: Psychic Symbiont 2018-06-26 23:57:16 +01:00
austinio7116
d9b1dfef76 M19 Satyr Enchanter 2018-06-26 23:32:12 +01:00
austinio7116
a56e760d36 M19 Regal Bloodlord 2018-06-26 23:31:45 +01:00
austinio7116
69b1e15d4c Arcane Encyclopedia added missed line 2018-06-26 22:25:06 +01:00
schnautzr
91ce89acef The blue cards. 2018-06-26 16:18:39 -05:00
schnautzr
dcb02999b4 Add support for enchantments that make things clones until unattached. 2018-06-26 23:18:17 +02:00
austinio7116
6782adc545 M19 Script Chaos Ward 2018-06-26 21:04:43 +01:00
austinio7116
10d42421d0 M19 Scripts Amulet of Safekeeping 2018-06-26 20:38:35 +01:00
austinio7116
9df831ea6f M19 scripts Arcane Encyclopedia 2018-06-26 20:00:37 +01:00
Michael Kamensky
82c4243049 Merge branch 'm19_scripts3' into 'master'
M19 more artifact cards

See merge request core-developers/forge!682
2018-06-26 17:09:57 +00:00
Hans Mackowiak
a34135a946 Merge branch 'master' into 'master'
Fix TokenKeywords separator on Resplendent Angel

See merge request core-developers/forge!683
2018-06-26 14:55:22 +00:00
austinio7116
e8300a0288 Gearsmith Guardian corrected picture path (although irrelevant) 2018-06-26 15:51:36 +01:00
austinio7116
681227e5fa switch to IsPresent 2018-06-26 13:44:03 +00:00
Luke Way
3c46d4eb96 Fix TokenKeywords separator on Resplendent Angel 2018-06-26 09:34:03 -04:00
austinio7116
37c43d085f Added M19 card Desecrated Tomb 2018-06-26 14:25:01 +01:00
austinio7116
6388f1ba1b Added M19 card Diamond Mare 2018-06-26 08:12:15 +01:00
austinio7116
9a0504714c Added M19 card Dragon's Hoard 2018-06-26 08:03:47 +01:00
austinio7116
03cc059bf7 Added M19 card Fountain of Renewal 2018-06-26 07:53:07 +01:00
austinio7116
1b8f1ec467 Added M19 card Gearsmith Guardian
(cherry picked from commit 043205b)
2018-06-26 07:42:37 +01:00
austinio7116
d6b29340c6 Added M19 card Marauder's Axe
(cherry picked from commit c995e7e)
2018-06-26 07:42:22 +01:00
austinio7116
904a852f1b Added M19 card Meteor Golem
(cherry picked from commit 8ef0a9f)
2018-06-26 07:42:09 +01:00
Michael Kamensky
ae252e0649 Merge branch 'master' into 'master'
Fixed a couple missing references.

See merge request core-developers/forge!680
2018-06-26 06:33:45 +00:00
Agetian
c02028f6f8 - Fixed a couple missing references. 2018-06-26 09:33:13 +03:00
Michael Kamensky
2905c2920c Merge branch 'm19scripts' into 'master'
M19 - 4 cards added

See merge request core-developers/forge!676
2018-06-26 06:05:39 +00:00
Michael Kamensky
ce66959034 Merge branch 'hexproofUpdate' into 'master'
Hexproof: recode ability to bypass Hexproof

See merge request core-developers/forge!677
2018-06-26 05:53:26 +00:00
Michael Kamensky
73d0c5e92d Merge branch 'master' into 'master'
Planeswalker achievements (GS1)

See merge request core-developers/forge!679
2018-06-26 05:45:34 +00:00
Agetian
5dcf2ffc76 - Contracted the GS1 achievement names.
- Corrected the card name.
2018-06-26 08:44:41 +03:00
Agetian
d21d7afe9a - Added planeswalker achievements for Global Series 1 (by Marek). 2018-06-26 08:40:59 +03:00
Michael Kamensky
1e0676562d Merge branch 'dom-puzzles' into 'master'
Added puzzle PS_DOM9 and improved PS_DOM4.

See merge request core-developers/forge!678
2018-06-26 05:38:29 +00:00
Agetian
ee2361d012 - Added a set spec in PS_DOM9. 2018-06-26 08:38:09 +03:00
Agetian
66c4769d78 - Added a way to add persistent mana in game states (for puzzles).
- Added basic logic for Evra, Halcyon Witness.
- Added puzzle PS_DOM9.
- Improved implementation for puzzle PS_DOM4.
2018-06-26 08:36:06 +03:00
austinio7116
0585181b28 Added M19 card Transmogrifying Wand 2018-06-26 06:27:56 +01:00
Hanmac
eb2208ce1e Hexproof: recode ability to bypass Hexproof 2018-06-26 07:27:17 +02:00
maustin
1968c3e1fc Merge remote-tracking branch 'Austin/m19scripts' into m19scripts 2018-06-26 06:26:21 +01:00
austinio7116
2b1a2340ff Removed detection tower as WIP 2018-06-26 06:24:58 +01:00
austinio7116
0703a6885f Delete transmogrifying_wand.txt 2018-06-26 05:23:02 +00:00
Michael Kamensky
76c197cd7b Merge branch 'optimizing' into 'master'
Reduce method call count.

See merge request core-developers/forge!672
2018-06-26 04:15:07 +00:00
Hans Mackowiak
0e194d7574 Merge branch 'cherry-pick-e171e802' into 'master'
Add support for pumping a player UntilLoseControlOfHost

See merge request core-developers/forge!675
2018-06-26 04:07:05 +00:00
austinio7116
488e3a1121 Added M19 card Sigiled Sword of Valeron 2018-06-25 23:24:17 +01:00
austinio7116
7cb02e3cb3 Added M19 card Skyscanner 2018-06-25 23:13:24 +01:00
austinio7116
9586e7f87e Added M19 card Suspicious Bookcase 2018-06-25 23:08:03 +01:00
Rob Schnautz
426e452ab5 convert tabs to spaces 2018-06-25 21:56:24 +00:00
schnautzr
7e7d83564f Add support for pumping a player UntilLoseControlOfHost
(cherry picked from commit e171e802c1)
2018-06-25 21:36:17 +00:00
schnautzr
e171e802c1 Add support for pumping a player UntilLoseControlOfHost 2018-06-25 16:29:50 -05:00
schnautzr
dfb713eb79 Add Suncleanser 2018-06-25 16:28:38 -05:00
austinio7116
b28001def5 Added M19 card Transmogrifying Wand 2018-06-25 21:39:28 +01:00
Michael Kamensky
c67194d5e6 Merge branch 'm19' into 'master'
M19 new cards

See merge request core-developers/forge!671
2018-06-25 18:26:33 +00:00
Michael Kamensky
5ce68f2d8b Merge branch 'setPT' into 'master'
Rework SetPT to use Map, and CharacteristicDefining

See merge request core-developers/forge!670
2018-06-25 17:46:22 +00:00
austinio7116
20b17cbdbe Added M19 card Detection Tower - corrected effect description 2018-06-25 07:01:02 +01:00
austinio7116
61697ea941 Added M19 card Detection Tower 2018-06-25 06:58:54 +01:00
Sol
edf5c132ce Merge branch 'patch-1' into 'master'
Shouldn't be an artifact. Thanks, Cryptix!

See merge request core-developers/forge!673
2018-06-25 03:28:03 +00:00
Rob Schnautz
dca2d7db48 Shouldn't be an artifact. Thanks, Cryptix! 2018-06-25 03:26:59 +00:00
schnautzr
d885390cab M19 white cards are scripted and tested except Suncleanser. 2018-06-24 22:21:54 -05:00
Rob Schnautz
8e54b0e742 Reduce method call count. 2018-06-24 21:18:33 +00:00
schnautzr
2401aa7ab5 White cards, A-M. Tested all of them. 2018-06-24 11:23:04 -05:00
Hanmac
6c0ca4ff08 Card: rework SetPT to use Map, and CharacteristicDefining 2018-06-24 17:52:39 +02:00
Michael Kamensky
366de65e8b Merge branch 'master' into 'master'
- Fix logic in DamageDealAi, update logic for Chandra, Fire of Kaladesh.

See merge request core-developers/forge!664
2018-06-24 06:23:32 +00:00
Agetian
efe493d179 - Unify AI logic checks. 2018-06-24 09:17:01 +03:00
Agetian
a4374b61d7 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2018-06-24 08:52:00 +03:00
Michael Kamensky
3b5fa24950 Merge branch 'patch-1' into 'master'
Add logging to indicate which card is causing Forge to hang.

See merge request core-developers/forge!666
2018-06-24 05:48:52 +00:00
Michael Kamensky
7411807922 Merge branch 'morphDieFix' into 'master'
GA: facedown from battlefield need reset

Closes #590

See merge request core-developers/forge!665
2018-06-24 05:48:14 +00:00
Michael Kamensky
efcdb5a300 Merge branch 'cherry-pick-886d7c29' into 'master'
Add edition file for M19.

See merge request core-developers/forge!669
2018-06-24 05:42:28 +00:00
schnautzr
0c06d78551 Add edition file for M19.
(cherry picked from commit 886d7c29d583b1e8b761a06fb3d432e9ebcf6d5a)
2018-06-24 01:03:51 +00:00
Rob Schnautz
573b4c9377 Add logging to indicate which card is causing Forge to hang. 2018-06-23 22:26:11 +00:00
Hanmac
f9bbe4b0d6 GA: facedown from battlefield need reset 2018-06-23 16:20:28 +02:00
Agetian
f192e1cf27 - Different logic update, seems more appropriate. 2018-06-22 13:26:34 +03:00
Agetian
07383f7c4c - Minor code tweak. 2018-06-22 12:07:20 +03:00
Agetian
4498547824 - Remove unused code. 2018-06-22 12:06:17 +03:00
Agetian
bd7da33e77 - Fix logic in DamageDealAi, update logic for Chandra, Fire of Kaladesh. 2018-06-22 12:04:08 +03:00
Michael Kamensky
7dc6573513 Merge branch 'flingCopyFix' into 'master'
SpellAbility: do not reset the PaidHash for copied spells

Closes #526

See merge request core-developers/forge!663
2018-06-22 07:21:49 +00:00
Hanmac
59c95f2ccf SpellAbility: do not reset the PaidHash for copied spells 2018-06-22 08:22:25 +02:00
Michael Kamensky
ce2841b05e Merge branch 'migrate_global_series' into 'master'
Migrate upcoming for Global Series set

See merge request core-developers/forge!661
2018-06-21 03:39:48 +00:00
Chris H
bdc44a4395 Migrate upcoming 2018-06-20 22:02:07 -04:00
Hans Mackowiak
f5c119e27c Merge branch 'playerProtection' into 'master'
Player: fixed Protection from everything

See merge request core-developers/forge!660
2018-06-20 19:10:03 +00:00
Hanmac
785c8ecdd8 Player: fixed Protection from everything 2018-06-20 20:03:26 +02:00
Michael Kamensky
58275457de Merge branch 'dom-puzzles' into 'master'
Added puzzle PS_DOM8.

See merge request core-developers/forge!659
2018-06-19 06:11:38 +00:00
Agetian
fb7d3115ce - Added puzzle PS_DOM8. 2018-06-19 09:10:43 +03:00
Michael Kamensky
c96d9c2bf7 Merge branch 'damageUpdates' into 'master'
Damage updates

Closes #579, #582, #583, and #584

See merge request core-developers/forge!657
2018-06-18 18:40:09 +00:00
Michael Kamensky
0dff0c488b Merge branch 'master' into 'master'
CardThemedDeckBuilder: disable debug logging by default

See merge request core-developers/forge!658
2018-06-18 09:42:09 +00:00
Agetian
a740bc262e - CardThemedDeckBuilder: disable debug logging by default. 2018-06-18 12:40:02 +03:00
Hanmac
8ab986f2e9 updated Flaming Gambit 2018-06-18 08:23:59 +02:00
Hanmac
1bbe8cd17c cards: more damage map for cards that deal X to something and Y to something else 2018-06-18 08:18:38 +02:00
Hanmac
2169e15742 cards: unify 'damage dealt this way' cards 2018-06-18 07:29:06 +02:00
Michael Kamensky
fa590b5eb1 Merge branch 'master' into 'master'
Fixed a bug written on Forum.

See merge request core-developers/forge!650
2018-06-17 09:11:51 +00:00
Michael Kamensky
196ab76e30 Merge branch 'master' into 'master'
Fix compile.

See merge request core-developers/forge!656
2018-06-17 08:23:17 +00:00
Agetian
8329a0f653 - Fix compile. 2018-06-17 11:22:50 +03:00
Michael Kamensky
8b1c9e0b7d Merge branch 'deckgenerationbugfixes' into 'master'
Deckgenerationbugfixes

See merge request core-developers/forge!655
2018-06-17 08:21:10 +00:00
maustin
b9d18fd38c Merge remote-tracking branch 'Austin/deckgenerationbugfixes' into deckgenerationbugfixes 2018-06-17 09:11:03 +01:00
austinio7116
30656d38c4 Removing debugging flag 2018-06-17 09:10:09 +01:00
austinio7116
b9e8528029 Fixed bug where adding random cards to deck would mess up land count - also shuffling random cards before adding. This code is only executed for rare cases where the learnt archetypes have a very tight cardlist, so if cards are non-AI playable or get randomly removed from the pool, the generator has a way to validly fill the gap. 2018-06-17 08:33:10 +01:00
austinio7116
cb082f1e31 Updated modern LDA data now run with identical parameters to the other formats.
(cherry picked from commit f207dc9)
2018-06-17 08:33:10 +01:00
Michael Kamensky
f270e4d9cd Merge branch 'master' into 'master'
Fix a check in MayPlay not properly accounting for some data after LKI copy.

See merge request core-developers/forge!654
2018-06-17 07:12:19 +00:00
Agetian
ba5b48cb2b Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2018-06-17 10:11:13 +03:00
Agetian
407616c515 - Fixed Gonti, Lord of Luxury not allowing to cast exiled cards (and other similar issues, potentially) 2018-06-17 10:10:37 +03:00
austinio7116
29ae8f5446 Fixed bug where adding random cards to deck would mess up land count - also shuffling random cards before adding. This code is only executed for rare cases where the learnt archetypes have a very tight cardlist, so if cards are non-AI playable or get randomly removed from the pool, the generator has a way to validly fill the gap. 2018-06-17 07:30:41 +01:00
austinio7116
2cc8166583 Updated modern LDA data now run with identical parameters to the other formats.
(cherry picked from commit f207dc9)
2018-06-17 07:00:34 +01:00
Michael Kamensky
85bd53b0c6 Merge branch 'trickster' into 'master'
Remove Abilities and */* creatures

Closes #562

See merge request core-developers/forge!649
2018-06-17 05:45:41 +00:00
yataka\pochiel
7d04f015f4 Fixed a bug written on Forum.
https://www.slightlymagic.net/forum/viewtopic.php?f=52&t=6333&sd=d&start=15#p225086.
2018-06-17 02:08:57 +09:00
Hanmac
51fd3f7647 StaticAbility: CDA PT use timestamp 2018-06-16 11:16:25 +02:00
Hanmac
ba362ede36 CardFace: allow to parse PT with * 2018-06-16 11:13:00 +02:00
Michael Kamensky
193336f5f3 Merge branch 'ldaarchetypedeckgenerationchanges' into 'master'
Archetype based deck generation - Legacy and Vintage

See merge request core-developers/forge!648
2018-06-16 03:57:44 +00:00
austinio7116
5cf63692a8 Fixed legacy and vintage deck generation for gauntlets on desktop and android 2018-06-15 22:01:20 +01:00
austinio7116
4612dbbc59 Updated deck generation to include Legacy and Vintage - fixed a few bugs that lead to incorrect land counts and random cards being added. Fixed Snow land issue so Skred red should be more playable. 2018-06-15 21:51:26 +01:00
Michael Kamensky
84a82990fa Merge branch 'master' into 'master'
More SVar spam fixes.

Closes #591 and #593

See merge request core-developers/forge!647
2018-06-15 07:31:22 +00:00
Agetian
04af457676 - Added a couple missing references. 2018-06-15 10:27:26 +03:00
Agetian
708c43edbf - Fix AI spam related to Tetsuko, Umezawa Fugitive. 2018-06-15 10:25:01 +03:00
Michael Kamensky
93ee8f7c7b Merge branch 'master' into 'master'
AttachAi: fix parameter source for P/T calculation.

Closes #592

See merge request core-developers/forge!646
2018-06-15 07:17:22 +00:00
Agetian
ebe7d72789 - AttachAi: fix parameter source in pump target P/T calculation 2018-06-15 10:16:04 +03:00
Michael Kamensky
6fa5e16d5f Merge branch 'fightFix' into 'master'
FightEffect: some reworks to make it rules conform

Closes #589

See merge request core-developers/forge!643
2018-06-15 06:40:43 +00:00
Hanmac
78f652dd43 FightAi: hotfix for cards like Arena 2018-06-15 08:03:52 +02:00
austinio7116
10724a5161 Added Legacy and Vintage LDA data (plus updated standard)
(cherry picked from commit 8e9aeb0)
2018-06-15 06:53:00 +01:00
Sol
6c40e8214d Merge branch 'emergency-patch-1' into 'master'
Emergency patch: fix rare/mythic slot - unintentional bang and also redundancy

See merge request core-developers/forge!645
2018-06-14 02:27:14 +00:00
Rob Schnautz
a73bd2fa3d fix rare/mythic slot - unintentional bang and also redundancy 2018-06-14 02:25:29 +00:00
Hanmac
9ee851dabc FightEffect: some reworks to make it rules conform 2018-06-13 21:22:08 +02:00
Rob Schnautz
d9fba35f64 Merge branch 'cm2' into 'master'
Cm2

See merge request core-developers/forge!557
2018-06-13 18:14:48 +00:00
Rob Schnautz
60c62c4713 Update Commander Anthology Vol. II.txt 2018-06-13 18:13:18 +00:00
Hans Mackowiak
832db1d89d Merge branch 'schnautzr-master-patch-54169' into 'master'
RemoveCounter -> SubCounter

See merge request core-developers/forge!642
2018-06-13 14:28:49 +00:00
Rob Schnautz
91d34b404d RemoveCounter -> SubCounter 2018-06-13 14:18:23 +00:00
Hans Mackowiak
a744fea295 Merge branch 'patch-1' into 'master'
cards: fixed sorcerers_wand StackDescription

See merge request core-developers/forge!641
2018-06-13 12:26:46 +00:00
Hans Mackowiak
17951d9c7f cards: fixed sorcerers_wand StackDescription 2018-06-13 12:23:27 +00:00
Hans Mackowiak
ff4b5fad66 Merge branch 'fixEdition' into 'master'
small fixes

See merge request core-developers/forge!640
2018-06-13 06:26:41 +00:00
Hanmac
923bc0a567 small fixes 2018-06-13 08:25:43 +02:00
Michael Kamensky
13b729134a Merge branch 'GS1-cards' into 'master'
GS1 cards

See merge request core-developers/forge!638
2018-06-13 04:17:36 +00:00
Michael Kamensky
517d927391 Merge branch 'oracle-updates-functional' into 'master'
GS1 functional Oracle update

See merge request core-developers/forge!636
2018-06-13 04:06:11 +00:00
Michael Kamensky
fa9669894d Merge branch 'GS1' into 'master'
Global Series 1: Jiang Yanggu & Mu Yanling

See merge request core-developers/forge!637
2018-06-13 04:06:06 +00:00
Rob Schnautz
27ccd4859b Update leopard_spotted_jiao.txt 2018-06-13 03:34:15 +00:00
Rob Schnautz
850409ec62 Update ferocious_zheng.txt 2018-06-13 03:33:33 +00:00
Rob Schnautz
800782a3b4 Update armored_whirl_turtle.txt 2018-06-13 03:33:03 +00:00
Sol
59a34ae91e Merge branch 'patch-1' into 'master'
Not nice to call people names.

See merge request core-developers/forge!639
2018-06-13 02:55:10 +00:00
Rob Schnautz
5894806df5 Add new file 2018-06-13 02:40:22 +00:00
Rob Schnautz
56f225be68 Add new file 2018-06-13 02:37:26 +00:00
Rob Schnautz
c0fe6186e9 Add new file 2018-06-13 02:31:23 +00:00
Rob Schnautz
0e01478dbf Add new file 2018-06-13 02:30:56 +00:00
Rob Schnautz
668fb1e202 Add new file 2018-06-13 02:30:03 +00:00
Rob Schnautz
ceb2002374 fix cost 2018-06-13 02:28:22 +00:00
Rob Schnautz
3975b3bf6d fix cost 2018-06-13 02:27:56 +00:00
Rob Schnautz
1b8d38c417 Add new file 2018-06-13 02:17:23 +00:00
Rob Schnautz
60a94f6022 Add new file 2018-06-13 02:14:16 +00:00
Rob Schnautz
824e0f18f2 fix activated ability cost 2018-06-13 02:11:38 +00:00
Rob Schnautz
08e8e414fc Add new file 2018-06-13 02:10:51 +00:00
Rob Schnautz
0327cd73f1 fix activated ability cost 2018-06-13 02:09:11 +00:00
Rob Schnautz
46b65e835c Add new file 2018-06-13 02:07:03 +00:00
Rob Schnautz
0e47e160d6 Not nice to call people names. 2018-06-13 02:04:01 +00:00
Rob Schnautz
26068c3033 Add new file 2018-06-13 01:56:41 +00:00
Rob Schnautz
401b0db85f Add new file 2018-06-13 01:55:34 +00:00
Rob Schnautz
0f06c93efc Add new file 2018-06-13 01:55:15 +00:00
Rob Schnautz
d614402a07 Add new file 2018-06-13 01:54:29 +00:00
Rob Schnautz
81f2969375 Add new file 2018-06-13 01:45:56 +00:00
Rob Schnautz
ced97449f4 Add new file 2018-06-13 01:41:43 +00:00
Rob Schnautz
399d03239d Add new file 2018-06-13 01:27:27 +00:00
Rob Schnautz
5e85516728 Add new file 2018-06-13 01:23:45 +00:00
Rob Schnautz
ca2b7887f0 Add new file 2018-06-13 01:21:46 +00:00
Rob Schnautz
3c3662dc0b Add new file 2018-06-13 01:19:11 +00:00
Rob Schnautz
74566f1f4e Add new file 2018-06-13 01:16:38 +00:00
Rob Schnautz
dddd09efe7 Add new file 2018-06-13 01:12:41 +00:00
Rob Schnautz
f313de53c0 Add new file 2018-06-13 01:11:07 +00:00
Rob Schnautz
3ae582ef97 Add new file 2018-06-13 01:07:58 +00:00
Rob Schnautz
23cab1ef8e Add new file 2018-06-13 01:07:31 +00:00
Rob Schnautz
25380e0a5c Add new file 2018-06-13 00:33:14 +00:00
Rob Schnautz
e812f41ae7 Add new file 2018-06-13 00:28:26 +00:00
Rob Schnautz
6fe794baa9 Add new file 2018-06-13 00:19:57 +00:00
Rob Schnautz
8733c63c58 Add new file 2018-06-13 00:15:34 +00:00
Rob Schnautz
54712f0c66 Add new file 2018-06-13 00:05:26 +00:00
Rob Schnautz
4f82662da8 Add new file 2018-06-12 23:59:58 +00:00
Rob Schnautz
056d50b6ba Add new file 2018-06-12 23:40:11 +00:00
Rob Schnautz
3831f4b5e2 Add new file 2018-06-12 23:37:31 +00:00
Rob Schnautz
858b8731f8 Add new file 2018-06-12 23:23:53 +00:00
Rob Schnautz
142fd19dec Update prowling_pangolin.txt 2018-06-12 22:28:08 +00:00
Hans Mackowiak
9f89b3528b Merge branch 'patch-1' into 'master'
Update gideons_phalanx.txt

See merge request core-developers/forge!635
2018-06-12 08:44:14 +00:00
Hans Mackowiak
88446acb39 Update gideons_phalanx.txt 2018-06-12 08:43:52 +00:00
Michael Kamensky
d25a2774d3 Merge branch 'dom-puzzles' into 'master'
Added puzzle PS_DOM7.

See merge request core-developers/forge!634
2018-06-12 06:32:33 +00:00
Agetian
07d2b0aaf3 - PS_DOM7: better implementation. 2018-06-12 09:30:41 +03:00
Agetian
d68ee9c507 - Added puzzle PS_DOM7. 2018-06-12 08:58:32 +03:00
Michael Kamensky
47b9b226e1 Merge branch 'grothama' into 'master'
Grothama

See merge request core-developers/forge!632
2018-06-12 05:27:17 +00:00
Hanmac
db1479f6b9 Grothama: fixed card draw 2018-06-12 06:48:28 +02:00
Michael Kamensky
b42c2ad489 Merge branch 'bbd_migrate_upcoming' into 'master'
Migrating BBD Upcomings

See merge request core-developers/forge!633
2018-06-12 04:08:27 +00:00
Michael Kamensky
ea7b290850 Merge branch 'ldaarchetypedeckgenerationchanges' into 'master'
Archetype based deck generation

See merge request core-developers/forge!631
2018-06-12 04:08:17 +00:00
Chris H
d8415d85ef Migrating BBD Upcomings 2018-06-11 22:13:58 -04:00
Hanmac
4e85678560 cards: add Grothama 2018-06-11 22:20:16 +02:00
austinio7116
5e335b97fa Improved deck generation code to reduce constraints and allow the LDA archetypes to be more accurately sampled. Ensure Tron doesn't get broken. 2018-06-11 20:25:16 +01:00
Hanmac
fd5a969434 CardFactoryUtil: xcount added DamageDoneByPlayerThisTurn 2018-06-11 20:46:09 +02:00
Hanmac
b58b8e2051 Trigger: replace ORIGINALHOST in ABILITY 2018-06-11 20:45:10 +02:00
Hanmac
38eda65143 Card: store Damage done by Sources a Player control to card 2018-06-11 20:43:42 +02:00
austinio7116
ddf8ce3ac5 Update LDA model with more lower tier data 2018-06-11 18:22:23 +01:00
maustin
8de0472048 Improved deck generation code - ensure keycard is non-land for LDA deck generation and improve handling of basic lands to ensure no colour is missed. 2018-06-11 18:03:56 +01:00
austinio7116
75931881f7 Improved deck naming for LDA generated decks 2018-06-11 08:42:47 +01:00
austinio7116
6d24ddc8e6 Prune archetypes that are less than 0.1% of meta as these are largely not real decks, but shared characteristics of multiple decks or unique decks not suitable for random generation. 2018-06-11 08:41:51 +01:00
Michael Kamensky
54f5af72f2 Merge branch 'oracle-updates-nonfunctional' into 'master'
Oracle updates nonfunctional

See merge request core-developers/forge!629
2018-06-10 18:58:11 +00:00
Rob Schnautz
4d3cdaa26f Shard order 2018-06-10 18:21:06 +00:00
Michael Kamensky
e29cb17712 Merge branch 'master' into 'master'
Forge for Android 1.6.11.001

See merge request core-developers/forge!628
2018-06-10 04:05:24 +00:00
Agetian
eb22d38770 - Preparing Forge for Android publish 1.6.11.001 [incremental]. 2018-06-10 07:04:42 +03:00
Blacksmith
244d98d81f Clear out release files in preparation for next release 2018-06-10 00:47:30 +00:00
Blacksmith
e2c57c0973 [maven-release-plugin] prepare for next development iteration 2018-06-10 00:42:03 +00:00
Blacksmith
6ec968cd85 [maven-release-plugin] prepare release forge-1.6.11 2018-06-10 00:42:00 +00:00
Blacksmith
0a4270970d Update README.txt for release 2018-06-10 00:40:15 +00:00
Michael Kamensky
50f978bc5c Merge branch 'morePartner' into 'master'
More partner

See merge request core-developers/forge!626
2018-06-09 11:47:17 +00:00
Agetian
2543dd0bae - Minor typo fix. 2018-06-09 14:46:21 +03:00
Agetian
2d9cdcaef6 Merge branch 'morePartner' of git.cardforge.org:core-developers/forge into morePartner 2018-06-09 14:35:42 +03:00
Hanmac
ed612574bc cards: add mentor and protege partner 2018-06-09 08:48:29 +02:00
Hanmac
20b28e3d06 cards: add soulblade partner 2018-06-09 08:48:29 +02:00
Hanmac
bfd667de00 cards: add chakram partner 2018-06-09 08:48:29 +02:00
Hanmac
a551612b89 cards: added blaring partner 2018-06-09 08:48:28 +02:00
Hans Mackowiak
7e4b909278 Merge branch 'patch-1' into 'master'
typo

See merge request core-developers/forge!627
2018-06-09 06:08:11 +00:00
Rob Schnautz
8448148f97 typo 2018-06-09 06:07:05 +00:00
Michael Kamensky
dc3efbc739 Merge branch 'filteredhands' into 'master'
Filtered Hands

See merge request core-developers/forge!607
2018-06-09 05:33:30 +00:00
Hanmac
2fd5ed045d cards: add mentor and protege partner 2018-06-08 23:28:40 +02:00
Hanmac
5c63c5fe1c cards: add soulblade partner 2018-06-08 23:09:34 +02:00
Hanmac
8f1ecbb12b cards: add chakram partner 2018-06-08 23:05:25 +02:00
Hanmac
8247399af8 cards: added blaring partner 2018-06-08 23:04:35 +02:00
Michael Kamensky
a89106211f Merge branch 'FriendOrFoe' into 'master'
Friend or foe

See merge request core-developers/forge!619
2018-06-08 08:00:43 +00:00
Michael Kamensky
e1202c6839 Merge branch 'ldaarchetypedeckgenerationchanges' into 'master'
Archetype based deck generation

See merge request core-developers/forge!625
2018-06-08 07:59:36 +00:00
Agetian
939e461c08 - Fixed Regna's Sanction. 2018-06-08 10:47:32 +03:00
Agetian
653329be09 - Fixed Virtus's Maneuver (take two) 2018-06-08 10:09:29 +03:00
Agetian
a2223fc6f9 - Fixed Virtus's Maneuver "friend" ability.
- Added a simple AI limiting NeedsToPlay svar.
2018-06-08 10:00:36 +03:00
Hanmac
2f02b03d6a Generous Patron: you need to be the Source of the counter for this effect to trigger 2018-06-08 06:55:00 +02:00
Hanmac
d851a4fc8d cards: add Virtus's Maneuver 2018-06-08 06:51:33 +02:00
Agetian
c54d24a362 - Simple limiting SVar for Khovrath's Fury AI until a better logic can be devised.
- Ideally should at least account for the fact that it should not overdraw cards and deck itself.
2018-06-08 07:18:49 +03:00
Rob Schnautz
c2797969d4 Shard order 2018-06-08 02:23:08 +00:00
austinio7116
f34dc83907 Changes to ensure LDA archetype decks are used in gauntlets rather than the old card-based ones. Ensured correct longpress function for generated decks in cardview on android. 2018-06-07 20:32:15 +01:00
austinio7116
85ad236693 Changed card-based deck generation to archetype based making better use of the new LDA models. Decks can now be selected by archetype with names generated from the source decklists. Archetypes are ordered by popularity.
(cherry picked from commit e993b00)
2018-06-07 19:55:41 +01:00
Hanmac
97cd2130b4 cards: add Khorvath's Fury 2018-06-07 07:34:24 +02:00
Michael Kamensky
3bf91bdde6 Merge branch 'dom-puzzles' into 'master'
Added puzzles PS_DOM5 and PS_DOM6.

See merge request core-developers/forge!624
2018-06-04 19:41:51 +00:00
Agetian
8304196533 - Added puzzles PS_DOM5 and PS_DOM6. 2018-06-04 22:41:01 +03:00
Agetian
d1fbe5c26d Merge remote-tracking branch 'upstream/FriendOrFoe' into FriendOrFoe
# Conflicts:
#	forge-gui/res/cardsfolder/upcoming/regnas_sanction.txt
2018-06-04 07:37:06 +03:00
Agetian
b43a313793 - Renamed two cards to match the current standard.
- Implemented basic AI playability of AssignGroup (Friend/Foe) cards based on NeedsToPlayVar.
- Will need separate AI logic for different cases to improve further (can be tested in canPlayAI).
2018-06-04 07:35:47 +03:00
Michael Kamensky
20b89b98ed Merge branch 'krav_ai' into 'master'
Simple AI support for Krav, the Unredeemed.

See merge request core-developers/forge!616
2018-06-04 03:53:02 +00:00
Michael Kamensky
6be0b30d04 Merge branch 'valduk' into 'master'
AI hints for Valduk

See merge request core-developers/forge!621
2018-06-04 03:51:37 +00:00
Rob Schnautz
8f4909ad9b Merge branch 'revert-1173dca6' into 'master'
Revert "Merge branch 'no_card' into 'master'"

See merge request core-developers/forge!622
2018-06-04 02:31:28 +00:00
Rob Schnautz
be778e451e Revert "Merge branch 'no_card' into 'master'"
This reverts merge request !620
2018-06-04 02:28:40 +00:00
maustin
5b1fac6734 AI hints for Valduk 2018-06-03 23:17:07 +01:00
Hanmac
d5b96c0051 cards: updated regna sanction 2018-06-03 22:02:54 +02:00
Hanmac
eaa5078521 CountersPut: use Placer 2018-06-03 22:01:56 +02:00
Hanmac
1ac0eb83cf cards: fixed zndrsplt judgment, add regna sanction 2018-06-03 20:45:34 +02:00
Hanmac
f1907012f7 fixed manacost 2018-06-03 19:01:28 +02:00
Hanmac
dc7f692f38 Game: Counters Put effects need to have Putter as Source 2018-06-03 18:21:02 +02:00
Hanmac
8dd5faf9c9 cards: add Zndrsplt's Judgement 2018-06-03 13:44:50 +02:00
Hanmac
80963222d1 CopyPermanent: add Param for Chooser 2018-06-03 13:42:38 +02:00
Agetian
2a3456d0e2 - Corrected the base logic for AssignGroupAi.
- Added AILogic to Pir's Whim.
2018-06-03 07:58:34 +03:00
Agetian
01de70351b - Minor style fix. 2018-06-03 07:40:14 +03:00
Agetian
d002a9311c - Some logic update for Krav. Somewhat better handling of CostSacrifice inside DrawAi. 2018-06-03 07:37:03 +03:00
Michael Kamensky
1173dca6c5 Merge branch 'no_card' into 'master'
Visual improvements to the default card image system

See merge request core-developers/forge!620
2018-06-03 04:28:35 +00:00
Rob Schnautz
a9a9124547 Restoring for those buggier cards. 2018-06-02 22:44:04 +00:00
Rob Schnautz
aa616e557a Upload New File 2018-06-02 22:37:32 +00:00
Rob Schnautz
69c0aba875 Update ForgeConstants.java 2018-06-02 22:34:46 +00:00
Rob Schnautz
7fb93a4f1d Add new images to help identify cards. 2018-06-02 22:34:03 +00:00
Michael Kamensky
390b041cb2 Merge branch 'oracle-updates-functional' into 'master'
Oracle updates functional

See merge request core-developers/forge!618
2018-06-02 19:02:38 +00:00
Michael Kamensky
0dfbfffe76 Merge branch 'patch-1' into 'master'
add "secret" cards

See merge request core-developers/forge!617
2018-06-02 19:01:35 +00:00
Hanmac
ae5bb38c92 cards: add Pir Whim as example 2018-06-02 20:37:00 +02:00
Hanmac
ccb5e6d6a5 AssignGroup API: use for Friend or Foe 2018-06-02 20:36:37 +02:00
Hanmac
e97b814089 chooseSingleSpellAbility has map params now 2018-06-02 20:34:04 +02:00
Hanmac
6cb14c2568 Card: add removeRemembered for Iterable 2018-06-02 20:24:03 +02:00
Rob Schnautz
b142ff53b5 Add new NO_CARD images to constants 2018-06-02 18:21:56 +00:00
schnautzr
6326b1c185 "opponent or planeswalker" 2018-06-02 12:31:40 -05:00
schnautzr
20b6d2c27b "opponent or planeswalker" 2018-06-02 12:26:33 -05:00
schnautzr
bacacdcb45 "opponent or planeswalker" 2018-06-02 12:24:37 -05:00
schnautzr
101521833d "opponent or planeswalker" 2018-06-02 12:13:09 -05:00
schnautzr
ff11d7156d "opponent or planeswalker" 2018-06-02 12:11:33 -05:00
schnautzr
a797a595c8 "opponent or planeswalker" 2018-06-02 12:09:54 -05:00
schnautzr
874757a79a "opponent or planeswalker" 2018-06-02 12:06:55 -05:00
schnautzr
08d7287099 "opponent or planeswalker" 2018-06-02 12:04:08 -05:00
schnautzr
487e717924 "opponent or planeswalker" 2018-06-02 11:58:24 -05:00
Rob Schnautz
730d9ebb70 Prevent booster from producing the secret versions of Will and Rowan 2018-06-02 16:44:53 +00:00
Rob Schnautz
4793cf77da add BBD RareMythic pool for boosters 2018-06-02 16:42:40 +00:00
Rob Schnautz
2c3069b5a2 add "secret" cards 2018-06-02 16:25:44 +00:00
Agetian
176c5f1871 - Simple AI support for Krav, the Unredeemed. 2018-06-02 16:44:28 +03:00
Michael Kamensky
38e47bbc5c Merge branch 'master' into 'master'
Minor tweak for Will and Rowan Kenrith

See merge request core-developers/forge!615
2018-06-02 03:57:00 +00:00
Agetian
f7a0f528d2 - Minor tweak for Rowan Kenrith / Will Kenrith. 2018-06-02 06:55:37 +03:00
Michael Kamensky
6482c8e05a Merge branch 'patch' into 'master'
BBD: Added Bonus Round and Generous Patron

See merge request core-developers/forge!614
2018-06-02 03:54:28 +00:00
Michael Kamensky
881905cf21 Merge branch 'oracle-updates-functional' into 'master'
Oracle updates functional

See merge request core-developers/forge!613
2018-06-02 03:53:57 +00:00
OgreBattlecruiser
e531f7300e BBD: Added Bonus Round and Generous Patron 2018-06-02 03:46:22 +01:00
schnautzr
c78411b7ee "opponent or planeswalker" 2018-06-01 19:48:30 -05:00
schnautzr
017deaaad5 "opponent or planeswalker" 2018-06-01 19:46:07 -05:00
schnautzr
433508b70b "opponent or planeswalker" 2018-06-01 19:42:12 -05:00
schnautzr
539c66779f "opponent or planeswalker" 2018-06-01 19:39:34 -05:00
schnautzr
83dfd3c739 "player or planeswalker" 2018-06-01 19:26:15 -05:00
schnautzr
3c17326928 "player or planeswalker" 2018-06-01 19:00:39 -05:00
schnautzr
ac5e585387 "player or planeswalker" 2018-06-01 18:57:10 -05:00
schnautzr
499f90b7a2 "player or planeswalker" 2018-06-01 18:52:04 -05:00
schnautzr
b0b16feb1f "player or planeswalker" 2018-06-01 18:35:06 -05:00
schnautzr
7d13720dad "player or planeswalker" 2018-06-01 18:27:17 -05:00
schnautzr
82dcccb5b3 "player or planeswalker 2018-06-01 18:18:28 -05:00
schnautzr
a3efd3dfe6 "player or planeswalker" 2018-06-01 18:08:02 -05:00
schnautzr
c870edd9f2 "player or planeswalker" 2018-06-01 16:41:53 -05:00
schnautzr
5de048a81a "player or planeswalker" 2018-06-01 16:34:00 -05:00
Michael Kamensky
00dcf5c2c6 Merge branch 'patch' into 'master'
Added some BBD cards

See merge request core-developers/forge!610
2018-06-01 04:18:16 +00:00
Michael Kamensky
e89c39d2e1 Merge branch 'oracle-updates-functional' into 'master'
Oracle updates functional

See merge request core-developers/forge!611
2018-06-01 04:17:33 +00:00
Michael Kamensky
ccd3e87403 Merge branch 'javadoc' into 'master'
Add javadoc compatible comments

See merge request core-developers/forge!612
2018-06-01 04:15:26 +00:00
Rob Schnautz
6e2f668e72 Add javadoc compatible comments 2018-06-01 04:02:25 +00:00
Hans Mackowiak
4896e47b41 Merge branch 'partnerWithCommander' into 'master'
Partner with commander

See merge request core-developers/forge!600
2018-05-31 18:44:10 +00:00
Hanmac
0b72fee8a3 Player: use sameTeam logic 2018-05-31 20:42:16 +02:00
Agetian
56e5bfa234 - Krav: for now, marked the activated ability as AI-unplayable and added a TODO. 2018-05-31 21:36:06 +03:00
schnautzr
d9deb6149c "player or planeswalker" 2018-05-31 13:35:46 -05:00
Rob Schnautz
3c9bf4f9c8 Upload New File 2018-05-31 17:55:27 +00:00
Rob Schnautz
e70f89b67c Upload New File 2018-05-31 17:55:15 +00:00
Rob Schnautz
4521cc88fa Upload New File 2018-05-31 17:55:01 +00:00
Rob Schnautz
5cd3a38fc1 Upload New File 2018-05-31 17:54:44 +00:00
Rob Schnautz
4067aca8fc Upload New File 2018-05-31 17:54:31 +00:00
Rob Schnautz
7fdc63c8a5 Upload New File 2018-05-31 17:54:12 +00:00
Rob Schnautz
c29cdcb757 Upload New File 2018-05-31 17:53:46 +00:00
Rob Schnautz
f4443ea97b Upload New File 2018-05-31 17:53:12 +00:00
Rob Schnautz
a83e193133 Replace with images that help with visual identification 2018-05-31 17:52:26 +00:00
OgreBattlecruiser
b985642e5e Added Arcane Artisan 2018-05-31 18:28:13 +01:00
schnautzr
d49e20cfa4 "player or planeswalker" 2018-05-31 11:14:06 -05:00
OgreBattlecruiser
af34fda5aa Added Rowan and Bramble Sovereign 2018-05-31 16:53:00 +01:00
schnautzr
7f7d2bf1dd "player or planeswalker" 2018-05-31 10:45:23 -05:00
Hans Mackowiak
8dbbfd5c07 Merge branch 'no_card.jpg' into 'master'
Update to M15 style

See merge request core-developers/forge!609
2018-05-31 05:45:25 +00:00
Rob Schnautz
02ae4d8460 Update to M15 style 2018-05-31 05:22:22 +00:00
schnautzr
e34adad368 "player or planeswalker" 2018-05-30 11:58:23 -05:00
schnautzr
55ec69ec79 typo 2018-05-30 11:05:25 -05:00
schnautzr
a9cf83aec2 "player or planeswalker" 2018-05-30 10:59:34 -05:00
Hans Mackowiak
35fc6ee211 Merge branch 'fixtests' into 'master'
Fixed broken tests due to oracle changes

See merge request core-developers/forge!608
2018-05-30 07:54:24 +00:00
austinio7116
e61c2f1554 Fixed broken tests due to oracle changes 2018-05-30 08:44:01 +01:00
austinio7116
e6af43b060 Implemented hand filtering properly using deck land ratio
Made hand filtering optional and disabled by default
2018-05-30 07:19:54 +01:00
maustin
e11fe9db8b Merge remote-tracking branch 'Austin/filteredhands' into filteredhands 2018-05-30 06:53:33 +01:00
austinio7116
14ba4e6894 Removed flicker when filtering starting hand 2018-05-30 06:50:25 +01:00
austinio7116
18e2d127be Experimental MTG Arena style double hand filtering 2018-05-30 06:50:25 +01:00
Hanmac
b77d191754 Pir: fixed mana cost 2018-05-30 07:18:07 +02:00
Hanmac
b04f644242 cards: Regna & Krav, fixed YourTeamGainedLife 2018-05-30 07:17:04 +02:00
Hanmac
570677fe15 Player: added LifeGainedByTeamThisTurn logic 2018-05-30 07:17:04 +02:00
Hanmac
a5848f4467 FlipCoin: fixed if it has no SubAbility 2018-05-30 07:17:04 +02:00
Hanmac
b956760d1e cards: Zndrsplt & Okaun 2018-05-30 07:17:04 +02:00
Hanmac
3118aa7b35 cards: add Pir and Toothy 2018-05-30 07:17:04 +02:00
swordshine
9b08ace35c Merge branch 'updateddeckgendata' into 'master'
Pre pro tour deckgeneration update

See merge request core-developers/forge!605
2018-05-30 01:11:20 +00:00
swordshine
39d1a7b3da Merge branch 'oracle-updates-functional' into 'master'
Oracle updates functional

See merge request core-developers/forge!604
2018-05-30 01:10:48 +00:00
swordshine
f0216cb3da Merge branch 'patch-1' into 'master'
Update forerunner_of_the_heralds.txt

See merge request core-developers/forge!606
2018-05-30 01:08:50 +00:00
Rob Schnautz
03a45a1185 Update forerunner_of_the_heralds.txt 2018-05-30 00:14:38 +00:00
austinio7116
f580d49a56 Pre pro tour deckgeneration update 2018-05-29 23:48:59 +01:00
schnautzr
284829fc65 prevent damage to "any target" 2018-05-29 17:24:06 -05:00
schnautzr
29e5316439 "ValidTgts\$ Player" 2018-05-29 15:55:44 -05:00
schnautzr
eedd169f15 "ValidTgts\$ Player" 2018-05-29 15:52:14 -05:00
schnautzr
d0227a7f99 "ValidTgts\$ Player" 2018-05-29 15:45:37 -05:00
schnautzr
8f6ba8553a "any target" 2018-05-29 15:27:09 -05:00
Rob Schnautz
0c9d134429 Merge branch 'oracle-c' into 'oracle-updates-functional'
Oracle c

See merge request core-developers/forge!603
2018-05-29 19:29:41 +00:00
schnautzr
e1aa757a3d "any target" 2018-05-29 14:29:11 -05:00
Hanmac
69d3db30cd Cards: Oracle update C 2018-05-29 21:23:59 +02:00
schnautzr
3906cf9cc0 Merge branch 'oracle-updates-functional' of https://git.cardforge.org/core-developers/forge into oracle-updates-functional 2018-05-29 14:13:26 -05:00
schnautzr
779680195d "any target" 2018-05-29 14:12:59 -05:00
Rob Schnautz
25d4e0e524 Merge branch 'oracle-b' into 'oracle-updates-functional'
Oracle b

See merge request core-developers/forge!602
2018-05-29 19:12:22 +00:00
Hanmac
06b7f2d677 Cards: Oracle update B 2018-05-29 21:08:05 +02:00
schnautzr
fdf58e82a3 "any target" 2018-05-29 13:55:36 -05:00
schnautzr
025b6db6ef Merge branch 'oracle-updates-functional' of https://git.cardforge.org/core-developers/forge into oracle-updates-functional 2018-05-29 13:48:08 -05:00
schnautzr
c5a62a46a8 "any target" 2018-05-29 13:47:35 -05:00
Rob Schnautz
f99bc762a9 Merge branch 'oracle-a' into 'oracle-updates-functional'
Oracle a

See merge request core-developers/forge!601
2018-05-29 18:46:06 +00:00
Hanmac
2b0fdec1d0 cards: Oracle-A 2018-05-29 20:43:04 +02:00
schnautzr
cfaf795bff Merge branch 'master' of https://git.cardforge.org/core-developers/forge into oracle-updates-functional 2018-05-29 13:08:57 -05:00
schnautzr
247ed0e597 "any target" 2018-05-29 13:07:19 -05:00
schnautzr
4a0f58862f "any target" 2018-05-29 13:00:26 -05:00
schnautzr
fe8ab69652 "any target" 2018-05-29 12:12:52 -05:00
schnautzr
82e4dd2cfd "any target" 2018-05-29 11:49:51 -05:00
schnautzr
08c6c7f725 "any target" 2018-05-29 11:39:36 -05:00
Michael Kamensky
9388a7c825 Merge branch 'oracle-updates-functional' into 'master'
"any target"

See merge request core-developers/forge!599
2018-05-29 03:40:25 +00:00
schnautzr
55c9783414 "any target" 2018-05-28 21:39:35 -05:00
Michael Kamensky
7cebee11a5 Merge branch 'oracle-updates-functional' into 'master'
Oracle updates functional

See merge request core-developers/forge!598
2018-05-28 20:07:30 +00:00
Michael Kamensky
f927cb41e0 Merge branch 'master' into 'master'
BBD: Added some cards

See merge request core-developers/forge!597
2018-05-28 20:04:16 +00:00
schnautzr
f53c7f5f78 "any target" 2018-05-28 10:50:25 -05:00
swordshine
8f3aec9ec1 - Added some cards 2018-05-28 23:02:18 +08:00
swordshine
1b7bdd2e57 - Added Demon/Angel Partners 2018-05-28 22:40:34 +08:00
schnautzr
4e04d7e28f "any target" 2018-05-28 09:34:35 -05:00
schnautzr
99de0acc8f Merge branch 'oracle-updates-functional' of https://git.cardforge.org/core-developers/forge into oracle-updates-functional 2018-05-28 09:27:18 -05:00
schnautzr
0bba96c1b7 "any target" 2018-05-28 09:26:53 -05:00
schnautzr
84e473c5cb Merge branch 'oracle-updates-nonfunctional' of https://git.cardforge.org/core-developers/forge into oracle-updates-nonfunctional 2018-05-28 09:25:33 -05:00
schnautzr
f91faf28d8 ", puts it onto the battlefield tapped, then shuffles their library" 2018-05-28 09:24:43 -05:00
swordshine
52d3318a9c - Updated some scripts for multiplayer 2018-05-28 21:35:19 +08:00
swordshine
cec608b8d0 - Added Will Kenrith 2018-05-28 21:33:45 +08:00
Michael Kamensky
1c559ff538 Merge branch 'partnerWith' into 'master'
Partner with

See merge request core-developers/forge!596
2018-05-27 14:54:26 +00:00
Michael Kamensky
23b067ee95 Merge branch 'oracle-updates-functional' into 'master'
Oracle updates functional

See merge request core-developers/forge!594
2018-05-27 14:54:17 +00:00
Michael Kamensky
009af245dc Update scourge_of_valkas.txt 2018-05-27 14:43:57 +00:00
Michael Kamensky
02f7ff8979 Merge branch 'master' into 'master'
BBD: Added some cards

See merge request core-developers/forge!595
2018-05-27 14:41:28 +00:00
Agetian
053726519c - Simple AI logic extension to make the Partner With commanders AI-playable. 2018-05-27 17:29:57 +03:00
Hanmac
e031ac8176 PartnerWith: extended DeckFormat Predicate 2018-05-27 15:52:18 +02:00
Hanmac
ef96037555 Partner with: fixed , to ; replace 2018-05-27 14:56:45 +02:00
Hanmac
3c1edcef4e cards: added Sylvia and Khorvath as Partner 2018-05-27 13:42:48 +02:00
Hanmac
6481842482 Partner with: added Logic into CardRules and added Trigger 2018-05-27 13:42:16 +02:00
swordshine
048737d9dd - Added some cards 2018-05-27 19:25:37 +08:00
Rob Schnautz
6b040f9bb8 Fix Oracle text (script was already changed) 2018-05-27 05:06:01 +00:00
schnautzr
9a57f1aca5 "any target" 2018-05-26 23:50:10 -05:00
schnautzr
3f99525715 "any target" 2018-05-26 23:12:52 -05:00
Michael Kamensky
0b5067b6e8 Merge branch 'cubes' into 'master'
Added Card Kingdom Starter Cube

See merge request core-developers/forge!593
2018-05-27 04:08:22 +00:00
schnautzr
124a9cec6c "any target" 2018-05-26 19:54:18 -05:00
schnautzr
2c21bd0ffb "any target" 2018-05-26 19:47:21 -05:00
schnautzr
e8cf3a1828 "any target" 2018-05-26 19:26:57 -05:00
austinio7116
faa322f4a7 Added Card Kingdom Starter Cube 2018-05-26 23:36:54 +01:00
maustin
cb1debc8f4 Merge remote-tracking branch 'Austin/filteredhands' into filteredhands
# Conflicts:
#	forge-game/src/main/java/forge/game/GameAction.java
2018-05-26 23:32:35 +01:00
Michael Kamensky
0264571cce Merge branch 'historicformats' into 'master'
Remove Alpha formats

See merge request core-developers/forge!586
2018-05-26 17:47:04 +00:00
schnautzr
35569a69b7 Remove Alpha formats 2018-05-26 12:21:31 -05:00
schnautzr
9a9f5ec268 Merge branch 'master' of https://git.cardforge.org/core-developers/forge into historicformats 2018-05-26 12:19:27 -05:00
Michael Kamensky
2a69451834 Merge branch 'master' into 'master'
Possibility Storm: puzzle PS_DOM4

See merge request core-developers/forge!591
2018-05-26 16:32:26 +00:00
Agetian
5f560c2266 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2018-05-26 19:29:34 +03:00
Michael Kamensky
10be0a93a4 Merge branch 'tokenRCtrl' into 'master'
Token & CopyPermanent: fixed RE changing controller

See merge request core-developers/forge!590
2018-05-26 16:28:52 +00:00
Hanmac
907825f53b Token & CopyPermanent: fixed RE changing controller 2018-05-26 18:19:15 +02:00
Agetian
755d2476e0 - Move the methods in GameState a little. 2018-05-26 18:45:12 +03:00
Agetian
101727b5da - For now, implement PS_DOM4 with four Islands on the AI's battlefield (more consistent for the AI to be able to cast Crafty Cutpurse across phases). 2018-05-26 18:37:34 +03:00
Agetian
2de0a4d164 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2018-05-26 18:29:43 +03:00
Michael Kamensky
32d276cb7e Merge branch 'cardTypeIntrinsic' into 'master'
Card type intrinsic

See merge request core-developers/forge!588
2018-05-26 15:26:20 +00:00
Hanmac
95cf25d531 Ageless Sentinels: fixed RemoveCreatureTypes 2018-05-26 17:13:54 +02:00
Agetian
7653f50f9d - Support mana pool modification via GameState. Update PS_DOM4 accordingly. 2018-05-26 17:45:11 +03:00
Hanmac
599407ff14 Cards: more on Animate Types, remove OverwriteTypes 2018-05-26 16:35:07 +02:00
Agetian
b4d57200a6 - Added puzzle PS_DOM4. 2018-05-26 16:48:07 +03:00
Agetian
4e15c2ddd4 - GameState: improve to handle cards that enchant a player (Trespasser's Curse). 2018-05-26 16:47:13 +03:00
Michael Kamensky
1a8b0bded3 Merge branch 'master' into 'master'
BBD: Added some cards

See merge request core-developers/forge!587
2018-05-26 13:27:45 +00:00
swordshine
9650bacca5 - Added some cards 2018-05-26 21:26:30 +08:00
Hanmac
ba29d1cfd9 Cards: use new Remove-Types Params and RemoveIntrinsicAbilities 2018-05-26 15:07:52 +02:00
Hanmac
51a0a9e70a CardType: fixed seting specific SubTypes 2018-05-26 15:07:52 +02:00
austinio7116
70a82fbc45 Removed flicker when filtering starting hand 2018-05-26 13:53:17 +01:00
Michael Kamensky
144f8944e1 Merge branch 'damageAnyTargetFix' into 'master'
fixed any Target Damage

See merge request core-developers/forge!589
2018-05-26 12:34:36 +00:00
Hanmac
8d3c4ff443 add TargetedOrController for spells that target player or planeswalker, extend OwnedBy and ControlledBy 2018-05-26 13:39:48 +02:00
Hanmac
31113133fc Oracle: update base code for their effects 2018-05-26 13:38:55 +02:00
Hanmac
2bf72bca6e fixed any Target Damage 2018-05-26 13:11:29 +02:00
swordshine
1b01336514 - Added some cards 2018-05-26 18:14:24 +08:00
austinio7116
d52deee44e Experimental MTG Arena style double hand filtering 2018-05-26 08:00:50 +01:00
swordshine
d4c25caeef - Added some cards 2018-05-26 13:31:43 +08:00
swordshine
614e98d32a - Updated TypeLists 2018-05-26 13:14:01 +08:00
swordshine
00360e55d2 - Added some cards 2018-05-26 13:02:23 +08:00
Michael Kamensky
8597956aeb Merge branch 'battlebond' into 'master'
Battlebond

See merge request core-developers/forge!585
2018-05-26 04:14:41 +00:00
Michael Kamensky
8c898b79b8 Merge branch 'oracle-updates-functional' into 'master'
Oracle updates functional

See merge request core-developers/forge!584
2018-05-26 03:58:48 +00:00
Michael Kamensky
544cfc5044 Merge branch 'oracle-updates-nonfunctional' into 'master'
Oracle updates nonfunctional

See merge request core-developers/forge!583
2018-05-26 03:58:01 +00:00
schnautzr
e3f0de8f84 Remove Alpha formats. These can be done easily with a filter. 2018-05-25 19:31:32 -05:00
Rob Schnautz
b160281cfe Update boosterboxes.txt 2018-05-25 23:53:15 +00:00
Rob Schnautz
321a44d0e9 booster info 2018-05-25 23:47:51 +00:00
Rob Schnautz
3bf52a1237 Add new file 2018-05-25 23:16:01 +00:00
Rob Schnautz
466297712d "choose a player" 2018-05-25 23:07:23 +00:00
Rob Schnautz
43f60a5927 "any target" 2018-05-25 22:46:09 +00:00
Rob Schnautz
2d47b99618 Add missing text 2018-05-25 22:32:20 +00:00
Rob Schnautz
cb66655c18 ", puts it onto the battlefield tapped, then shuffles their library." 2018-05-25 22:15:24 +00:00
Rob Schnautz
981dcade2b Update battle_mastery.txt 2018-05-25 22:08:02 +00:00
Rob Schnautz
fc15e64c54 Update aim_high.txt 2018-05-25 22:04:57 +00:00
Rob Schnautz
fdcb9bd7a4 "any target" 2018-05-25 21:56:03 +00:00
Rob Schnautz
e6f4661077 "any target" 2018-05-25 21:51:47 +00:00
Rob Schnautz
912f28c06a "target player or planeswalker" 2018-05-25 21:49:47 +00:00
Rob Schnautz
561d0fec79 "target player or planeswalker" 2018-05-25 21:46:09 +00:00
Rob Schnautz
eff5e56219 \n 2018-05-25 21:43:38 +00:00
Rob Schnautz
e594c6f11e "any target" 2018-05-25 21:33:47 +00:00
Michael Kamensky
c28e8a700d Merge branch 'oracle-updates-nonfunctional' into 'master'
Oracle updates nonfunctional

See merge request core-developers/forge!582
2018-05-25 05:20:14 +00:00
Michael Kamensky
617ff419b9 Update boundless_realms.txt 2018-05-25 05:19:57 +00:00
Michael Kamensky
d9b5d19e8f Update harvest_season.txt 2018-05-25 05:19:32 +00:00
Michael Kamensky
2d26abac38 Update boundless_realms.txt 2018-05-25 05:15:19 +00:00
schnautzr
2d31093d2d "reveal them, put them into your hand, then shuffle your library." 2018-05-25 00:11:01 -05:00
schnautzr
7f30db66b6 "reveal it, put it into your hand, then shuffle your library." 2018-05-25 00:08:44 -05:00
schnautzr
a05c75f838 ", put them onto the battlefield tapped, then shuffle your library." 2018-05-25 00:05:29 -05:00
schnautzr
efae93345a ", put it onto the battlefield tapped, then shuffle your library." 2018-05-25 00:02:01 -05:00
schnautzr
e236a107c9 ", put it onto the battlefield, then shuffle your library." 2018-05-24 23:58:45 -05:00
schnautzr
b033ab0bba ", put them onto the battlefield tapped, then shuffle your library." 2018-05-24 23:56:25 -05:00
swordshine
e80893e4a0 Merge branch 'oracle-updates-nonfunctional' into 'master'
Oracle updates nonfunctional

Closes #219

See merge request core-developers/forge!581
2018-05-25 04:19:19 +00:00
schnautzr
f9aa7ece1c "they" 2018-05-24 17:30:59 -05:00
schnautzr
1000b73ecb "they drew" 2018-05-24 17:21:24 -05:00
schnautzr
4105d54582 "they sacrifice two lands." 2018-05-24 17:19:55 -05:00
schnautzr
270bd3cb73 "they may put that card on the bottom of that library." 2018-05-24 17:18:13 -05:00
schnautzr
b581d722c3 "they discarded" 2018-05-24 17:16:20 -05:00
schnautzr
bc84022d52 "they sacrifice a creature." 2018-05-24 17:14:55 -05:00
schnautzr
508b1c6c49 "nearest them" 2018-05-24 17:13:22 -05:00
schnautzr
d66ce972f0 "they" 2018-05-24 17:10:57 -05:00
schnautzr
adc7ea417e "they exile a nonland card." 2018-05-24 17:07:11 -05:00
schnautzr
3a5bffdb0c "nonland" 2018-05-24 17:05:16 -05:00
schnautzr
2c27c2754f "than they do" 2018-05-24 17:02:27 -05:00
schnautzr
9877a8d15f "they exile it." 2018-05-24 17:00:21 -05:00
schnautzr
50d9003ea8 "they lose the flip" 2018-05-24 16:57:53 -05:00
schnautzr
172600a94d "they discard a card." 2018-05-24 16:50:48 -05:00
schnautzr
1b8bc63b07 "they" 2018-05-24 16:48:51 -05:00
schnautzr
bb12706151 "their life" 2018-05-24 16:46:50 -05:00
schnautzr
18f241f036 "their upkeep" 2018-05-24 16:45:16 -05:00
schnautzr
65aa1b8d89 "they lose 5 life and you" 2018-05-24 16:42:52 -05:00
schnautzr
b91517812a "they get a poison counter." 2018-05-24 16:39:22 -05:00
schnautzr
66c9b2ae25 "they could cast a sorcery." 2018-05-24 16:36:46 -05:00
schnautzr
9739dffd71 "they were chosen." 2018-05-24 16:33:56 -05:00
schnautzr
8cd5948375 "they lose 2 life." 2018-05-24 16:28:49 -05:00
schnautzr
22d9c09403 "they lose 3 life." 2018-05-24 16:26:15 -05:00
schnautzr
336b1f309a "they choose." 2018-05-24 16:21:52 -05:00
schnautzr
86fd37b80b "they shuffle their library." 2018-05-24 16:20:00 -05:00
schnautzr
54fbb78bea "they gain 1 life." 2018-05-24 16:17:47 -05:00
schnautzr
b6a2d9ed73 "they exile a nonland card." 2018-05-24 16:15:15 -05:00
schnautzr
118d2333ec "their turn" 2018-05-24 16:12:49 -05:00
schnautzr
3ee4535ebd "their chosen" 2018-05-24 16:10:22 -05:00
schnautzr
8fb779b7ec "they exiled" 2018-05-24 16:08:10 -05:00
schnautzr
295bfbe8a3 "they discard four cards." 2018-05-24 16:05:12 -05:00
schnautzr
ccc2629e47 "they've" 2018-05-24 16:01:59 -05:00
schnautzr
ac3bec4332 "one they draw" 2018-05-24 15:56:52 -05:00
schnautzr
03551d6968 "their draw" 2018-05-24 15:54:42 -05:00
schnautzr
f6c9bdb5e2 "they put the top card of their library into their graveyard." 2018-05-24 15:51:41 -05:00
schnautzr
8cc0744698 "they may copy this spell and may" 2018-05-24 15:47:51 -05:00
schnautzr
5f7bcb4d69 "their first" 2018-05-24 15:45:30 -05:00
schnautzr
88538a500f "they put it onto the battlefield." 2018-05-24 15:42:48 -05:00
schnautzr
871065bdca "their untap" 2018-05-24 15:38:51 -05:00
schnautzr
26c2845d4f "they discard a land card." 2018-05-24 15:36:22 -05:00
schnautzr
bee2be36cf "they chose" 2018-05-24 15:32:55 -05:00
schnautzr
6897f36e03 "they lose 5 life." 2018-05-24 15:29:04 -05:00
schnautzr
117ff56bbc "they sacrifice that artifact." 2018-05-24 15:24:57 -05:00
schnautzr
d3ff88f934 "they" 2018-05-24 15:21:06 -05:00
schnautzr
93b5eb0b1e "he or she draws a card and reveals it" -> "they draw a card and reveal it" 2018-05-24 14:59:14 -05:00
schnautzr
712a907f34 "his or her next" -> "their next" 2018-05-24 14:55:45 -05:00
schnautzr
e13ae86a05 "his or her card" -> "their card" 2018-05-24 14:53:14 -05:00
schnautzr
41e048d382 "he or she owns" -> "they own" 2018-05-24 14:50:43 -05:00
schnautzr
87d236828f "he or she controlled" -> "they controlled" 2018-05-24 14:47:32 -05:00
schnautzr
8d73b8e132 "his or her choice" -> "their choice" 2018-05-24 14:44:48 -05:00
schnautzr
a1400a0318 "his or her pile" -> "their pile" 2018-05-24 14:39:25 -05:00
schnautzr
65e88caed4 "he or she creates" -> "they create" 2018-05-24 14:36:14 -05:00
schnautzr
14413dcdd7 "damage to him or her" -> "damage to them/that player" 2018-05-24 14:28:23 -05:00
schnautzr
5c90cf5a2c "his or her control" -> "their control" 2018-05-24 14:02:46 -05:00
schnautzr
d2473fe3a6 "He or she sacrifices" -> "They sacrifice" 2018-05-24 14:00:21 -05:00
schnautzr
64725a4c41 "his or her last turn" -> "their last turn" 2018-05-24 13:58:03 -05:00
schnautzr
4330d95866 "If he or she does" -> "If they do" 2018-05-24 13:54:50 -05:00
schnautzr
6897d1d31a "his or her starting life" -> "their starting life" 2018-05-24 13:51:45 -05:00
schnautzr
63bb900889 "his or her opponent" -> "their opponent" 2018-05-24 13:49:19 -05:00
schnautzr
c1ca1f0a99 "he or she does" => "they do" 2018-05-24 13:46:23 -05:00
schnautzr
98762a6c77 "his or her graveyard" -> "their graveyard" 2018-05-24 13:41:59 -05:00
schnautzr
fd98f9b7f9 "his or her hand" -> "their hand" 2018-05-24 13:37:56 -05:00
schnautzr
946a1e9cb4 "he or she pays" -> "they pay" 2018-05-24 13:34:48 -05:00
schnautzr
244b52a8b3 "he or she controls" -> "they control" 2018-05-24 13:31:34 -05:00
schnautzr
e82103ba94 replace "his or her library" with "their library" 2018-05-24 13:28:08 -05:00
schnautzr
cf792b7ed5 "they" 2018-05-24 13:18:29 -05:00
schnautzr
31b76892cd "they" 2018-05-24 12:54:15 -05:00
schnautzr
ba942bff3d "they" 2018-05-24 12:48:55 -05:00
schnautzr
c5a351f6ca "they" 2018-05-24 12:33:53 -05:00
schnautzr
27e9a56d04 "they" 2018-05-24 12:19:33 -05:00
Michael Kamensky
8b00ab6bf5 Merge branch 'oracle-updates-nonfunctional' into 'master'
Oracle updates nonfunctional

See merge request core-developers/forge!579
2018-05-24 03:12:45 +00:00
Michael Kamensky
86cbc8a613 Merge branch 'oracle-updates-functional' into 'master'
Oracle updates functional

See merge request core-developers/forge!578
2018-05-24 03:12:43 +00:00
Michael Kamensky
69c17e9b4c Merge branch 'patch-1' into 'master'
Field of Ruin fix

See merge request core-developers/forge!577
2018-05-24 03:01:09 +00:00
Michael Kamensky
01d91402fc Merge branch 'brawlruleschanges' into 'master'
Using brawl banlist wherever needed

See merge request core-developers/forge!576
2018-05-24 03:00:27 +00:00
Rob Schnautz
281a8c7e66 Update vigor.txt 2018-05-24 01:44:40 +00:00
Rob Schnautz
ef758638af Update veteran_explorer.txt 2018-05-24 01:41:14 +00:00
Rob Schnautz
0b2331b0e6 Functional update (Dominaria) 2018-05-24 01:27:36 +00:00
Rob Schnautz
8d114e42f1 Fix function, Oracle, stack text, and update Oracle. 2018-05-24 00:28:41 +00:00
austinio7116
89fef80d05 Using brawl banlist wherever needed 2018-05-23 22:18:58 +01:00
austinio7116
c9a0058042 Experimental MTG Arena style double hand filtering 2018-05-23 21:35:33 +01:00
Hans Mackowiak
740884847f Merge branch 'patch-1' into 'master'
Integer.max can't be used use Math.max

See merge request core-developers/forge!575
2018-05-23 20:17:35 +00:00
Hans Mackowiak
5a68bc4b79 Integer.max can't be used use Math.max 2018-05-23 20:17:07 +00:00
Michael Kamensky
f3006fd8e2 Merge branch 'patch-1' into 'master'
fixed Deprecated warning

Closes #565

See merge request core-developers/forge!574
2018-05-23 05:25:09 +00:00
Rob Schnautz
0b616164c4 Battlebond Oracle updates (non-functional) 2018-05-22 22:14:48 +00:00
Rob Schnautz
3cfaa8ad99 Battlebond Oracle update (nonfunctional) 2018-05-22 22:09:45 +00:00
Hans Mackowiak
187256cdb7 fixed Deprecated warning 2018-05-22 15:25:42 +00:00
Michael Kamensky
adc2d95b1f Merge branch 'suspendHaste' into 'master'
Suspend: reworked suspend

See merge request core-developers/forge!573
2018-05-22 05:39:40 +00:00
Hanmac
2973214976 CardTraitBase: make sVars cloneable, use in SpellAbility 2018-05-21 22:45:03 +02:00
Michael Kamensky
f8418f689f Merge branch 'mergebranch' into 'master'
Fixed BoosterUtils Extended format issue in quests by renaming final extended format .txt file

See merge request core-developers/forge!572
2018-05-21 19:25:22 +00:00
Hanmac
8d03b23eb4 Suspend: reworked suspend, no need for SuspendCast anymore, Haste is done as Pump Effect 2018-05-21 18:14:20 +02:00
austinio7116
d1dd1c7530 Fixed BoosterUtils Extended format issue in quests by renaming final extended format .txt file 2018-05-21 17:13:02 +01:00
Michael Kamensky
9aeed6ac31 Merge branch 'counterGameCheck' into 'master'
Put Counter Effects check Game State

Closes #548

See merge request core-developers/forge!568
2018-05-20 17:14:43 +00:00
Michael Kamensky
fcc8a8f9ce Merge branch 'mergebranch' into 'master'
Fixed missing Extended format required for quests if Historic mode switched off in settings

See merge request core-developers/forge!571
2018-05-20 17:09:53 +00:00
austinio7116
67e5ae9ee0 Attempt to fix BoosterUtils Extended format issue in quests 2018-05-20 17:59:52 +01:00
austinio7116
2b49f70ae3 Attempt to fix BoosterUtils Extended format issue in quests 2018-05-20 17:55:45 +01:00
Michael Kamensky
7813483a08 Merge branch 'dom-puzzles' into 'master'
Added puzzle PS_DOM3.

See merge request core-developers/forge!569
2018-05-20 10:12:39 +00:00
Agetian
045b303deb - Added puzzle PS_DOM3. 2018-05-20 13:12:10 +03:00
Hanmac
57af402bcf cards: PutCounter use Triggered LKICopy 2018-05-20 11:37:28 +02:00
Hanmac
e4b7c105b1 cards: update Monstrosity using TriggeredCount 2018-05-20 11:33:43 +02:00
Hanmac
3b5d273409 CounterEffects: use gameState and timestamp checks 2018-05-20 11:33:18 +02:00
austinio7116
07c29999c6 Merge branch 'historicmerge' into 'master'
Completed historic formats from Schnautzr, the Wurmcaller

See merge request core-developers/forge!567
2018-05-20 06:51:21 +00:00
austinio7116
9dc7632e68 Completed historic formats from Schnautzr, the Wurmcaller 2018-05-20 07:47:38 +01:00
Michael Kamensky
442ffdcb37 Merge branch 'brawlcolourless' into 'master'
Brawl deck generators make use of new colourless rules for basic lands

See merge request core-developers/forge!565
2018-05-20 04:19:46 +00:00
Michael Kamensky
f02ba82471 Merge branch 'basicLandAbility' into 'master'
Card: move Abilities to Card and only create them once

See merge request core-developers/forge!560
2018-05-20 03:40:05 +00:00
schnautzr
f628a4aa65 All historic DCI formats through Dominaria are now added. 2018-05-19 20:30:48 -05:00
schnautzr
20b80bced6 Formats through Ixalan. 2018-05-19 20:10:09 -05:00
schnautzr
801193edde Formats through Kaladesh. 2018-05-19 19:08:00 -05:00
schnautzr
8eaf76734e Formats through Shadows over Innistrad. Marked Ice Age and Extended as retired and deleted several Extended formats that never existed. 2018-05-19 18:23:42 -05:00
schnautzr
9a1dcd3cda Formats through Battle for Zendikar. 2018-05-19 17:26:25 -05:00
austinio7116
553cec6d60 Brawl deck generators make use of new colourless rules for basic lands 2018-05-19 22:25:01 +01:00
schnautzr
c333fec6c4 Formats through Khans of Tarkir. Fixed Extended formats. Rotated Theros Extended formats. 2018-05-19 15:56:59 -05:00
schnautzr
7e9efd9c30 Formats through Theros. 2018-05-19 15:02:56 -05:00
Hanmac
8e7f055b59 try Spell.performanceMode only for other player, enable Bestow and Morph 2018-05-19 10:26:45 +02:00
Hanmac
ece779c88e unSuppress basicLandAbilities 2018-05-19 10:21:01 +02:00
Hanmac
c1da970c47 Card: move Abilities to Card and only create them once 2018-05-19 10:21:01 +02:00
Michael Kamensky
3505047c9b Merge branch 'brawlruleschanges' into 'master'
New Brawl rules updates

See merge request core-developers/forge!564
2018-05-19 05:25:26 +00:00
schnautzr
1338af9720 Formats through Return to Ravnica. 2018-05-18 20:25:15 -05:00
schnautzr
e0dd4d3b4f Formats through Innistrad and 1/1/12 changes. Rename Duelists' Convocation to DCI. 2018-05-18 19:26:56 -05:00
austinio7116
1458184500 New Brawl rules updates 2018-05-18 23:29:15 +01:00
austinio7116
52fe47c3ee Merge branch 'master' into 'master'
Prevent views flickering during gameplay from state-based effects.

See merge request core-developers/forge!563
2018-05-18 18:58:24 +00:00
Alexei Svitkine
1a81f24c45 remove unrelated changes 2018-05-18 10:58:52 -04:00
Alexei Svitkine
63210a42b0 fix merge 2018-05-18 10:58:02 -04:00
Alexei Svitkine
09900a3457 Merge remote-tracking branch 'upstream/master' 2018-05-18 10:53:23 -04:00
Alexei Svitkine
7071be93f7 fix unit tests with anti flicker fixes, by having CurrentState be
settable when frozen if it's null
2018-05-18 10:40:30 -04:00
Alexei Svitkine
96a7d7d0d2 Prevent views flickering during gameplay when state-based affects
change card state (e.g. Painter's Servant, Ambush Commander, etc)
2018-05-18 10:02:16 -04:00
Alexei Svitkine
2974e33751 fix error when trying to set up game state and the file
doesn't exist
2018-05-18 09:57:12 -04:00
schnautzr
854205f86f Formats through Zendikar. 2018-05-17 19:48:18 -05:00
Michael Kamensky
74e4f2aa4a Merge branch 'ldaupdates' into 'master'
Update to LDA model pruning - updated LDA data

See merge request core-developers/forge!561
2018-05-17 08:57:08 +00:00
Michael Kamensky
eadb5aebb7 Merge branch 'assorted-fixes' into 'master'
Fixed Volrath's Shapeshifter Oracle text.

See merge request core-developers/forge!562
2018-05-17 06:30:05 +00:00
Agetian
ae1de32654 - Fixed Volrath's Shapeshifter Oracle text. 2018-05-17 09:28:11 +03:00
austinio7116
b3d70f1f0d Update to LDA model pruning - updated LDA data 2018-05-17 06:41:37 +01:00
schnautzr
b7651abf4a Add formats through Tenth Edition. 2018-05-16 19:49:58 -05:00
Sol
7005a2fbe4 Merge branch 'removeprototypes' into 'master'
Temporarily removed prototype files from formats to avoid invalid duplicates

See merge request core-developers/forge!559
2018-05-16 12:13:58 +00:00
austinio7116
e9dd24e6c8 Temporarily removed prototype files 2018-05-16 06:56:09 +01:00
Michael Kamensky
ed034bdec7 Merge branch 'performancemode' into 'master'
Bandaid temporary performance preference to disable additional static abilities…

See merge request core-developers/forge!558
2018-05-16 04:43:30 +00:00
Sol
bbd8c0c752 Merge branch 'historicformats' into 'master'
Formatting and preparation of historicformats

See merge request core-developers/forge!342
2018-05-16 01:40:34 +00:00
schnautzr
1bb807e302 Merge branch 'historicformats' of https://git.cardforge.org/core-developers/forge into historicformats
# Conflicts:
#	forge-gui/res/formats/Historic/prototypes/release-dates.txt
2018-05-15 19:09:58 -05:00
schnautzr
114185ae1e Added formats through Ravnica: City of Guilds (10/20/05). "Type 1.5" becomes Legacy. 2018-05-15 19:05:48 -05:00
austinio7116
b91791c747 Temporarily removed prototype files - made historic formats disabled by default 2018-05-15 20:55:37 +01:00
austinio7116
e51fdd8c56 Bandaid temporary performance preference to disable additional static abilities checks when checking mayplay to support better performance on Mobile and for multiplayer
(cherry picked from commit 3e6a2e9)
2018-05-15 20:51:09 +01:00
austinio7116
8c695fd4d9 Added configurable option to enable/disable historic formats 2018-05-15 19:46:28 +01:00
Hans Mackowiak
7823c28540 Merge branch 'EquipRework' into 'master'
Equip Bludgeon Brawl

See merge request core-developers/forge!553
2018-05-15 09:15:10 +00:00
Rob Schnautz
7aed102941 Fill in gaps and numbers 2018-05-14 22:58:41 +00:00
Rob Schnautz
15e8ac7640 Add Daretti 2018-05-14 21:00:11 +00:00
austinio7116
5e5d78e595 Merge branch 'patch-cardStateVisitor' into 'master'
Game: FIX CardStateVisitor

See merge request core-developers/forge!556
2018-05-14 17:51:59 +00:00
Hans Mackowiak
5bfb8aa8ac Game: FIX CardStateVisitor 2018-05-14 17:43:30 +00:00
Rob Schnautz
9345f7bb23 Adding all known information 2018-05-14 03:15:06 +00:00
Rob Schnautz
261da41e85 forgot to save before the commit :o 2018-05-14 01:52:16 +00:00
schnautzr
ee8c7ba84e Add prototypes folder containing more compact solution for evolving formats. 2018-05-13 16:54:01 -05:00
schnautzr
64e3718122 Add formats Mirrodin through Darksteel, along with 9/20 update, which renamed Type 1 to Vintage and replaced Type 1.5 with "Type 1.5". 2018-05-13 09:16:29 -05:00
Michael Kamensky
c0e715717b Merge branch 'oldcore' into 'master'
Latent Derichlet Allocation based deck generation update

See merge request core-developers/forge!555
2018-05-13 13:22:24 +00:00
austinio7116
4a4e38fdd0 Code cleanup for LDA 2018-05-13 07:09:41 +01:00
austinio7116
05bc65aba9 Removed old deckgen data 2018-05-13 07:00:18 +01:00
schnautzr
ac27e07225 cleanup 2018-05-12 22:09:26 -05:00
schnautzr
e9d8e9ba78 Merge branch 'historicformats' of https://git.cardforge.org/core-developers/forge into historicformats 2018-05-12 22:02:35 -05:00
schnautzr
53a521ecd2 Fixes to Alpha formats. Formats through 1/1/04 are done now. 2018-05-12 21:59:28 -05:00
austinio7116
ff12e5b088 Removed old deckgen data 2018-05-12 22:48:52 +01:00
austinio7116
2fada1da8d Added actual LDA deck generation data 2018-05-12 22:48:06 +01:00
austinio7116
c003ddeeac Code cleanup before merge 2018-05-12 22:45:49 +01:00
austinio7116
df7f893302 Added .txt to format file 2018-05-12 22:38:25 +01:00
maustin
21c41550f8 Merge remote-tracking branch 'Core/historicformats' into historicformatscopy
# Conflicts:
#	forge-gui/res/formats/Historic/Duelists' Convocation/pending verification/Block/Invasion Block Constructed, Apocalypse.txt
2018-05-12 22:33:28 +01:00
austinio7116
e38a73b590 Cleaned up parsing error in historic formats
(cherry picked from commit 553cf5f)
2018-05-12 22:32:55 +01:00
schnautzr
8e6755482a back to previous grouping, hadn't considered the breakage it might cause. 2018-05-12 14:33:22 -05:00
Hanmac
c96b918fad cards: update Equip cards, some oracle updates 2018-05-12 21:12:30 +02:00
schnautzr
ecb6a10c7f Add alpha-only formats through Onslaught, regroup sets 2018-05-12 14:04:37 -05:00
Hanmac
790a461622 CardType: add removeArtifactTypes for Bludgeon Brawl
Equip: rework it to make Equip legendary creature better, also use AttachAi
2018-05-12 21:03:15 +02:00
Michael Kamensky
ec94798cc2 Merge branch 'master' into 'master'
The Orazca region of Ixalan plane (in Planar Conquest) is essentially all-color.

See merge request core-developers/forge!552
2018-05-12 15:30:32 +00:00
Agetian
b94b1e3df1 - The Orazca region of Ixalan plane (in Planar Conquest) is essentially all-color. 2018-05-12 18:24:41 +03:00
Michael Kamensky
ef75e00184 Merge branch 'master' into 'master'
Fixed the AI trying to target Illusions from Jace, Cunning Castaway with buff spells.

Closes #556

See merge request core-developers/forge!551
2018-05-12 13:22:16 +00:00
Agetian
5c03d27538 - Fixed the AI trying to target Illusions from Jace, Cunning Castaway with buff spells.
- Fixed a NPE when a spell fizzles on a temporary object such as token for which getCardState would return null once it ceases to exist.
2018-05-12 16:20:27 +03:00
austinio7116
ccb1aa9ef0 Added some planeswalker variants to the Ixalan plane
(cherry picked from commit 9cf57e1)
2018-05-12 06:57:12 +01:00
austinio7116
54e2951e4b Pruned and sorted Ixalan plane plus added Orazca
(cherry picked from commit f6e66c5)
2018-05-12 06:56:58 +01:00
austinio7116
e8c6f29577 Refactored GA runners into GA classes, configured tournaments for GA, working Ixalan plane
(cherry picked from commit d7d3ea3)
2018-05-12 06:56:12 +01:00
austinio7116
e0880f1932 First shared version of Ixalan planar conquest
(cherry picked from commit b2fd38d)
2018-05-12 06:55:06 +01:00
austinio7116
465c450b6b Working GA code for planes or standard
(cherry picked from commit cfd2f66)
2018-05-12 06:54:14 +01:00
austinio7116
0d4088ff7a Added abstract GA code
(cherry picked from commit c534c14)
2018-05-12 06:36:56 +01:00
austinio7116
7e3b9ccc2d Second simple attempt at planar conquest generation code and example
(cherry picked from commit 234db8d)
2018-05-12 06:36:08 +01:00
austinio7116
d96d1f2ee9 First simple attempt at planar conquest generation code and example
(cherry picked from commit b11aa9b)
2018-05-12 06:34:55 +01:00
austinio7116
ed76e62d56 Removed actual LDA code as not Android compatible
(cherry picked from commit e930e9b)
2018-05-12 06:31:34 +01:00
austinio7116
43e4d8a914 Moved Archetype LDA loading from generic collections to separate classes to prepare for Android compatibility. Added modern genetic algorithm class.
(cherry picked from commit d8a4ad7)
2018-05-12 06:31:21 +01:00
austinio7116
714a77f451 Refactored IO for LDA dat files to store raw data as well as pruned topics. Current settings provide good standard decks.
(cherry picked from commit 6b981a0)
2018-05-12 06:30:50 +01:00
austinio7116
3f8651f586 Fully working LDA based deck generation for Standard and Modern
(cherry picked from commit 892ae23)
2018-05-12 06:30:37 +01:00
austinio7116
a25592ebfd Fixed some LDA zero indexed array issues so now it completes.
(cherry picked from commit 5ab7704)
2018-05-12 06:29:55 +01:00
austinio7116
4a131b63d8 Initial running LDA implementation
(cherry picked from commit 03c1b3e)
2018-05-12 06:29:38 +01:00
Michael Kamensky
2e8ee84485 Merge branch 'tokeninfo-color-fix' into 'master'
Fixed token color information processing which is critical for Puzzle Mode

See merge request core-developers/forge!548
2018-05-11 17:33:35 +00:00
Hans Mackowiak
fb1c545a98 Merge branch 'hasKeywordEnum' into 'master'
Card: add Keyword methods that work with Keyword Enum

See merge request core-developers/forge!535
2018-05-11 11:34:15 +00:00
Michael Kamensky
0a03261dbb Merge branch 'patch-5' into 'master'
Update crumble_to_dust.txt

See merge request core-developers/forge!550
2018-05-11 06:49:18 +00:00
Meerkov
1c3fc52b6a Update crumble_to_dust.txt 2018-05-11 05:52:17 +00:00
Agetian
b091b892e9 - Code simplification. 2018-05-10 22:08:14 +03:00
Agetian
74e84dc381 Merge remote-tracking branch 'origin/tokeninfo-color-fix' into tokeninfo-color-fix 2018-05-10 21:42:25 +03:00
Agetian
1d4d18566c - Fixed token color information processing which is critical for game states and Puzzle Mode (otherwise all tokens are colorless). 2018-05-10 21:42:11 +03:00
Agetian
4c083a127f Merge remote-tracking branch 'origin/tokeninfo-color-fix' into tokeninfo-color-fix 2018-05-10 21:38:07 +03:00
Sol
1df11a1f81 Merge branch 'assorted-fixes' into 'master'
Fixed slowdown when removing a card from the deck in Constructed deck editor with infinite catalog cardpool

See merge request core-developers/forge!545
2018-05-10 18:33:44 +00:00
Agetian
41698bfddd - Fixed token color information processing which is critical for game states and Puzzle Mode (otherwise all tokens are colorless). 2018-05-10 21:26:37 +03:00
Agetian
39d143cebc - Fixed token color information processing which is critical for game states and Puzzle Mode (otherwise all tokens are colorless). 2018-05-10 21:19:56 +03:00
Michael Kamensky
4576bb28ac Merge branch 'dom-puzzles' into 'master'
Possibility Storm Dominaria season: Added puzzle PS_DOM2.

See merge request core-developers/forge!547
2018-05-10 17:56:57 +00:00
Agetian
998efa11e3 - Added puzzle PS_DOM2. 2018-05-10 20:55:11 +03:00
Michael Kamensky
66870cdd05 Merge branch 'master' into 'master'
Battlebond dual lands

See merge request core-developers/forge!546
2018-05-10 17:06:20 +00:00
swordshine
42ead5b9eb - Battlebond dual lands 2018-05-10 22:07:51 +08:00
Hanmac
852e12c903 AI: fixed isCounterable canPayCost 2018-05-10 15:44:23 +02:00
Hanmac
058aabb666 KeywordEnum: replace more String Keywords checks with Enums 2018-05-10 11:53:41 +02:00
schnautzr
34ee393224 Finalize historic formats through Urza block. 2018-05-09 21:30:06 -05:00
Agetian
816660f80d - To avoid possibly unexpected behavior, check to ensure that the infinite pool already contains all the items requested. 2018-05-09 18:27:00 +03:00
Agetian
1e1a16a4f4 - Do not go through the process of adding items to a card pool if it's already infinite (fixes slowdown when removing a card from the deck in the Constructed deck editor) 2018-05-09 18:23:25 +03:00
Michael Kamensky
bf2eed7cf5 Merge branch 'master' into 'master'
Tetzimoc CountersPutAi: don't try to target creatures with Hexproof

See merge request core-developers/forge!544
2018-05-09 12:13:01 +00:00
Agetian
d13201fd47 - Tetzimoc CountersPutAi: don't try to target creatures which can't legally be targeted (e.g. Hexproof). 2018-05-09 15:12:30 +03:00
Michael Kamensky
5debe17b08 Merge branch 'master' into 'master'
Updating CHANGES.txt and planes.txt for the new Planar Conquest plane.

See merge request core-developers/forge!543
2018-05-09 07:04:52 +00:00
Agetian
aa34d92668 - Updating CHANGES.txt and planes.txt for the new Planar Conquest plane. 2018-05-09 10:03:59 +03:00
Michael Kamensky
279ff2c765 Merge branch 'ixalanplanarconquest' into 'master'
Ixalan planar conquest with AI generated/optimized duel opponents

See merge request core-developers/forge!542
2018-05-09 07:00:25 +00:00
austinio7116
1ebb4aa091 Ixalan planar conquest with AI generated/optimized duel opponents 2018-05-08 21:25:24 +01:00
Michael Kamensky
176705e703 Merge branch 'master' into 'master'
Make Tetzimoc, Primal Death actually AI playable

See merge request core-developers/forge!541
2018-05-08 17:13:21 +00:00
Agetian
82afad3b67 - Improve AI logic for Tetzimoc, Primal Death; ensure that the AI actually plays it (used not to play at all). 2018-05-08 20:11:47 +03:00
Agetian
5d43733add - Preparing Forge for Android publish 1.6.10.001 [incremental]. 2018-05-08 06:51:43 +03:00
schnautzr
e07b34f9cb Finalize formats through Weatherlight 2018-05-07 20:49:08 -05:00
Blacksmith
a1203b2c4a Clear out release files in preparation for next release 2018-05-08 00:53:34 +00:00
Blacksmith
f0ce6277ac [maven-release-plugin] prepare for next development iteration 2018-05-08 00:48:11 +00:00
Blacksmith
95bd867cee [maven-release-plugin] prepare release forge-1.6.10 2018-05-08 00:48:08 +00:00
Blacksmith
ac63687c33 Update README.txt for release 2018-05-08 00:46:40 +00:00
schnautzr
924e063168 Finalize historic editions through Fifth Edition 2018-05-07 18:09:34 -05:00
Michael Kamensky
8a52feaf6d Merge branch 'master' into 'master'
More NPE prevention in mobile Forge.

See merge request core-developers/forge!540
2018-05-07 20:19:33 +00:00
Agetian
773358e538 - More NPE prevention in mobile Forge. 2018-05-07 23:18:46 +03:00
Michael Kamensky
44fc9be4a2 Merge branch 'master' into 'master'
NPE prevention in mobile Forge related to choosing a variant

See merge request core-developers/forge!539
2018-05-07 20:08:50 +00:00
Agetian
697be27d4f - NPE prevention in LobbyScreen on mobile Forge when trying to choose a variant without having a constructed deck. 2018-05-07 23:07:47 +03:00
Michael Kamensky
fb23e5a663 Merge branch 'master' into 'master'
Planar Conquest: allow Chaos matches from custom user worlds.

See merge request core-developers/forge!538
2018-05-07 15:38:56 +00:00
Agetian
12631ffee6 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2018-05-07 18:38:00 +03:00
Agetian
cbce073227 - Planar Conquest: allow Chaos matches from custom user worlds. 2018-05-07 18:37:34 +03:00
Michael Kamensky
4103afc6cf Merge branch 'fix_ante_corr' into 'master'
Merging "Fix ante" by Friarsol (merge conflict resolved)

See merge request core-developers/forge!537
2018-05-07 03:59:55 +00:00
Agetian
c2ddc8cd87 Resolved merge conflict. 2018-05-07 06:57:13 +03:00
Chris H
52a5f3b0a4 Fix ante to work with RegisterdPlayer 2018-05-06 21:42:56 -04:00
Hanmac
91d80f49f4 Card: add Keyword methods that work with Keyword Enum 2018-05-06 16:54:53 +02:00
Michael Kamensky
e0b1bc7cd0 Merge branch 'asFlash' into 'master'
Code As through it has flash Effects

See merge request core-developers/forge!532
2018-05-06 08:56:15 +00:00
Hanmac
438d4f6e38 WrappedAbility: add AdditionalAbilityList methods too 2018-05-06 10:29:59 +02:00
Agetian
473fa088ce - Wrapped abilities should get additional abilities from the underlying SA (fixes MayFlashSac on something like Abattoir Ghoul crashing). 2018-05-06 11:12:09 +03:00
Agetian
e52b9d62f7 - AI should not attempt to cast MayFlashSac auras "as though they had flash" for now, it'll just always cast them in its own Upkeep and end up wasting them. 2018-05-06 11:10:42 +03:00
Hanmac
c7fb979fe2 Merge branch 'asFlash' of git.cardforge.org:core-developers/forge into asFlash 2018-05-06 09:03:34 +02:00
Hanmac
198e7adc47 MayFlashSac: rework using MayPlayNotSorcerySpeed 2018-05-06 08:55:10 +02:00
Hanmac
4cf79222f0 fix AiAttack with CHOSEN_FOG_EFFECT 2018-05-06 08:49:29 +02:00
Hanmac
94a9597963 fix player to RegisteredPlayer for AnteResult 2018-05-06 08:48:18 +02:00
Michael Kamensky
469649c0c4 Merge branch 'master' into 'master'
Clarification in AiCostDecision

See merge request core-developers/forge!534
2018-05-06 06:01:08 +00:00
Agetian
bd08c5ca9d - Clarification in AiCostDecision 2018-05-06 09:00:41 +03:00
Agetian
1296b24960 - A quick patch to make the AI compatible with MayFlashCost (may need a rewrite later when the optional costs are correctly implemented for the AI). 2018-05-06 08:00:51 +03:00
Hanmac
87b8ac38f2 Fix: fixed MayFlashSac 2018-05-05 18:47:20 +02:00
Michael Kamensky
51dca51014 Merge branch 'master' into 'master'
Fixed the AI failing to activate abilities such as Merchant's Dockhand (tapXType).

See merge request core-developers/forge!533
2018-05-05 15:42:39 +00:00
Agetian
07c448ba16 - Fixed the AI failing to activate abilities such as Merchant's Dockhand which may tap the card in the process which the AI is considering for a tapXType payment (which may lead to a misplay or a loop/hang). 2018-05-05 18:41:10 +03:00
Hanmac
0033edbeda Restriction: fix Bestow with check if it is already animated under lki 2018-05-05 16:47:48 +02:00
Hanmac
d7e701d7a5 SpellRestriction: fixed for LKI, moved Legendary Sorcery 2018-05-05 10:35:44 +02:00
Hanmac
63bd305250 Twilights call: fix Oracle 2018-05-05 00:10:34 +02:00
Hanmac
dc789addb9 Fix: add hand oracle to tawnos again 2018-05-05 00:05:05 +02:00
Hanmac
6b1c0a0eda cards: update cards with WithFlash option or using MaybeFlash keywords 2018-05-04 23:59:02 +02:00
Hanmac
a3f36bd7a4 Spell: do as though it has Flash effects 2018-05-04 23:49:30 +02:00
Hanmac
1258e24ee0 Static Ability: add Ferocious as Condition 2018-05-04 23:46:19 +02:00
Hanmac
f9832a51cf GameAction: check static, use card in preList if able 2018-05-04 23:42:43 +02:00
Hanmac
018a3d8c91 FCollection: extra get Function that checks for element in Collection 2018-05-04 23:39:31 +02:00
Michael Kamensky
ec1a4579e1 Merge branch 'deckbuildingfix' into 'master'
Fixed card-deck decks all getting a totally random card

See merge request core-developers/forge!531
2018-05-04 03:48:40 +00:00
Sol
bfde06f0d6 Merge branch 'seravy-ai-fixes' into 'master'
Reformatted/revised AI updates from Seravy: Fog AI, Protection from chosen color AI

See merge request core-developers/forge!503
2018-05-04 00:35:07 +00:00
austinio7116
b8c207ac2a Small improvements to genetic algorithms, merged Agetian's fixes for planar conquest 2018-05-03 20:40:59 +01:00
Michael Kamensky
9b2e54c001 Merge branch 'assorted-fixes' into 'master'
Fixed the AI activating Jace, Cunning Castaway +1 too late.

See merge request core-developers/forge!530
2018-05-03 15:58:53 +00:00
Agetian
62585848bd - Fixed the AI activating Jace, Cunning Castaway +1 too late. 2018-05-03 18:55:42 +03:00
Michael Kamensky
d47acc16a9 Merge branch 'dom-puzzles' into 'master'
Possibility Storm - Dominaria Season (minor update).

See merge request core-developers/forge!529
2018-05-03 04:51:17 +00:00
Agetian
a34e97af52 - Added puzzle PS_AERT (Aether Revolt Throwback Puzzle).
- Pruned the puzzle files PS_DOM0 and PS_DOM1 (removed unused empty definitions).
2018-05-03 07:50:26 +03:00
Michael Kamensky
319b587c1b Merge branch 'deckgendata' into 'master'
Updated Modern and Standard deck generation models with more Dominaria data

See merge request core-developers/forge!528
2018-05-03 04:00:24 +00:00
swordshine
07f0f06767 Merge branch 'dom-puzzles' into 'master'
Possibility Storm - Dominaria season

See merge request core-developers/forge!527
2018-05-03 02:37:53 +00:00
austinio7116
ac3513ac8a Updated Modern and Standard deck generation models with more Dominaria data 2018-05-02 23:03:08 +01:00
Agetian
5760520ca2 - Added puzzle PS_DOM0 (Gaea's Protector card preview puzzle).
- Some tweaks to PS_DOM1.
2018-05-02 23:17:31 +03:00
Agetian
236f456097 - Added puzzle PS_DOM1 (Possibility Storm - Dominaria #01). 2018-05-02 22:05:44 +03:00
Michael Kamensky
2965e0fc28 Merge branch 'master' into 'master'
Deck description update.

See merge request core-developers/forge!526
2018-05-02 14:34:08 +00:00
Agetian
0ccdbde2f7 - Deck description update. 2018-05-02 17:33:10 +03:00
Michael Kamensky
b2659d6c38 Merge branch 'master' into 'master'
Updating the deck Starswirl the Bearded 2.

See merge request core-developers/forge!525
2018-05-02 14:20:18 +00:00
Agetian
52b300650c - Updating the deck Starswirl the Bearded 2. 2018-05-02 17:19:20 +03:00
Michael Kamensky
0b05bde1e3 Merge branch 'master' into 'master'
Fixed Ranging Raptors trigger not being optional.

See merge request core-developers/forge!524
2018-05-02 13:18:13 +00:00
Agetian
b72b528cb3 - Fixed Ranging Raptors trigger not being optional. 2018-05-02 16:17:39 +03:00
Michael Kamensky
ab98c1df7d Merge branch 'dom-ai-hints' into 'master'
Dominaria: adding AI deckbuilding and gameplay hints, making Mox Amber AI playable.

See merge request core-developers/forge!509
2018-05-02 12:23:43 +00:00
Agetian
bc63cdd52a - Code simplification. 2018-05-02 15:07:04 +03:00
Michael Kamensky
ada5aef26f Merge branch 'patch-3' into 'master'
Update olivias_bloodsworn.txt

See merge request core-developers/forge!521
2018-05-02 08:16:15 +00:00
Michael Kamensky
df1050668f Merge branch 'patch-4' into 'master'
Update forerunner_of_slaughter.txt

See merge request core-developers/forge!522
2018-05-02 08:15:39 +00:00
Michael Kamensky
4dc13f9417 Merge branch 'patch-5' into 'master'
Update time_vault.txt

See merge request core-developers/forge!523
2018-05-02 08:15:11 +00:00
Meerkov
d71da723a0 Update time_vault.txt 2018-05-02 06:48:57 +00:00
Meerkov
c5ce102f4d Update forerunner_of_slaughter.txt 2018-05-02 06:47:54 +00:00
Meerkov
3d291444bc Update olivias_bloodsworn.txt 2018-05-02 06:47:22 +00:00
Agetian
270e25083a - Trigger an unexpected behavior warning instead of crashing the game. 2018-05-02 08:35:09 +03:00
Agetian
b6f55309ae - Some formatting. 2018-05-02 08:19:01 +03:00
Agetian
4afeaeb170 - Updating AiAttackController to use an AI hint SVar for Awakening and Prophet of Kruphix. 2018-05-02 08:04:31 +03:00
Michael Kamensky
932dde8177 Merge branch 'patch-1' into 'master'
Update leaf_arrow.txt

See merge request core-developers/forge!519
2018-05-02 04:44:20 +00:00
Meerkov
41b4ae9aae Update leaf_arrow.txt 2018-05-02 04:21:35 +00:00
Agetian
9d66d36a78 - Clarification in ManaReflectedEffect. 2018-05-02 07:15:26 +03:00
Agetian
a4133a07a8 - Cleanup and var rename in ManaReflectedEffect. 2018-05-02 07:14:57 +03:00
Michael Kamensky
f73d139aa7 Merge branch 'quest-decks' into 'master'
Quest Mode decks: updating Ixalan decks with RIX cards, adding two quest opponents focused on Dominaria cards.

See merge request core-developers/forge!510
2018-05-02 04:11:42 +00:00
Michael Kamensky
d6ca320c61 Merge branch 'patch-6' into 'master'
Update banefire.txt

See merge request core-developers/forge!518
2018-05-02 04:06:48 +00:00
Agetian
bbf645bc6b - Removed an empty line. 2018-05-02 07:05:07 +03:00
Agetian
ea547dfd4d - Removed an empty line. 2018-05-02 07:04:33 +03:00
Meerkov
d990f245d5 Update banefire.txt 2018-05-02 04:03:33 +00:00
Agetian
bd0bad4254 - Revert changes by Seravy to PlayerControllerHuman: a matter of opinion, wasn't discussed. 2018-05-02 07:02:38 +03:00
Michael Kamensky
a0c2c41203 Merge branch 'patch-4' into 'master'
Update chandras_defeat.txt

See merge request core-developers/forge!515
2018-05-02 03:59:26 +00:00
Michael Kamensky
74606f6b04 Merge branch 'patch-5' into 'master'
Update crumble_to_dust.txt

See merge request core-developers/forge!516
2018-05-02 03:59:16 +00:00
Michael Kamensky
cca8f84f61 Merge branch 'patch-6' into 'master'
Update living_lore.txt

See merge request core-developers/forge!517
2018-05-02 03:59:06 +00:00
Agetian
b3aaf2e0e4 - Style fix. 2018-05-02 06:57:52 +03:00
Agetian
5bf954c503 - Grow from the Ashes: added a missing reference. 2018-05-02 06:56:15 +03:00
Agetian
c66a8c20d2 - Integrating a reformatted fix by Seravy which makes Mox Amber AI playable (as well as Reflecting Pool) and fixes auto mana payment for these and other cards that use ManaReflected. 2018-05-02 06:55:59 +03:00
Agetian
d664f8abcd - Integrating a reformatted fix by Seravy which makes Mox Amber AI playable (as well as Reflecting Pool) and fixes auto mana payment for these and other cards that use ManaReflected. 2018-05-02 06:55:45 +03:00
Meerkov
403b1f3c39 Update living_lore.txt 2018-05-02 03:49:11 +00:00
Meerkov
38697f0835 Update crumble_to_dust.txt 2018-05-02 03:46:25 +00:00
Meerkov
0b5beddd51 Update chandras_defeat.txt 2018-05-02 03:40:45 +00:00
Michael Kamensky
a6c3a642f8 Merge branch 'patch-3' into 'master'
Update demigod_of_revenge.txt

See merge request core-developers/forge!514
2018-05-02 03:34:25 +00:00
Michael Kamensky
5e8d761a41 Merge branch 'patch-2' into 'master'
Update dark_supplicant.txt

See merge request core-developers/forge!513
2018-05-02 03:34:11 +00:00
Michael Kamensky
b4fd663329 Merge branch 'patch-1' into 'master'
Update marsh_flitter.txt

See merge request core-developers/forge!512
2018-05-02 03:33:59 +00:00
Meerkov
124c1632af Update demigod_of_revenge.txt 2018-05-02 03:20:12 +00:00
Meerkov
3867287c0f Update dark_supplicant.txt 2018-05-02 03:19:03 +00:00
Meerkov
c1c0424af9 Update marsh_flitter.txt 2018-05-02 03:13:43 +00:00
Michael Kamensky
cd9d70baee Merge branch 'master' into 'master'
Fixed Elfhame Druid Oracle text.

See merge request core-developers/forge!511
2018-05-01 20:26:12 +00:00
Agetian
13aec8f621 - Fixed Elfhame Druid Oracle text. 2018-05-01 23:25:21 +03:00
Agetian
7b5e7afd6d - Added quest opponent deck Princess Zelda 1.
- Minor stylistic tweaks to Princess Zelda 2, Starswirl the Bearded 2.
2018-05-01 22:51:06 +03:00
Agetian
a586be2c41 - Minor style fix. 2018-05-01 22:26:19 +03:00
Agetian
4630623d35 - Added the quest opponent deck Starswirl the Bearded 2.
- Minor fix in Princess Zelda 2.
2018-05-01 22:25:25 +03:00
Agetian
71a2e68551 - Dominaria: adding AI deckbuilding and gameplay hints. 2018-05-01 21:10:33 +03:00
Agetian
ef71e037f2 - Added quest opponent deck Princess Zelda 2. 2018-05-01 20:10:47 +03:00
Michael Kamensky
df30218f55 Merge branch 'assorted-fixes' into 'master'
- Added AI NeedsToPlay hint to Triumph of Gerrard.

See merge request core-developers/forge!508
2018-05-01 16:11:27 +00:00
Agetian
e8a84ad571 - Added AI NeedsToPlay hint to Triumph of Gerrard. 2018-05-01 19:10:46 +03:00
Agetian
43dfbed157 - Updating Morkus Rex 2 and 3 quest opponent decks with RIX cards. 2018-05-01 18:30:16 +03:00
Michael Kamensky
8b8605b773 Merge branch 'assorted-fixes' into 'master'
Added AI NeedsToPlay hint to Song of Freyalise.

See merge request core-developers/forge!507
2018-05-01 15:23:31 +00:00
Agetian
9ef27b2699 - Added AI NeedsToPlay hint to Song of Freyalise. 2018-05-01 18:21:44 +03:00
Michael Kamensky
faad9e0d2c Merge branch 'assorted-fixes' into 'master'
- Added AI logic to Shalai, Voice of Plenty.

See merge request core-developers/forge!506
2018-05-01 07:33:46 +00:00
Agetian
6a9ba00aca - Added AI logic to Shalai, Voice of Plenty. 2018-05-01 10:32:53 +03:00
Agetian
5329138d0f - Further tweaks to Guybrush Threepwood 2 quest deck. 2018-05-01 08:46:33 +03:00
Agetian
ed0109af94 - Updating Guybrush Threepwood decks with several RIX cards. 2018-05-01 08:27:14 +03:00
Sol
92556d5cb2 Merge branch 'revert-64e8dd35' into 'master'
Revert "Merge branch 'temp_dev6' into 'master'"

See merge request core-developers/forge!505
2018-05-01 01:11:41 +00:00
Sol
1a3308017a Revert "Merge branch 'temp_dev6' into 'master'"
This reverts merge request !499
2018-05-01 01:11:29 +00:00
Sol
64e8dd35c2 Merge branch 'temp_dev6' into 'master'
Allow for Seeded RNG in simulation games!

See merge request core-developers/forge!499
2018-05-01 01:01:30 +00:00
Michael Kamensky
bf5d8b5472 Merge branch 'assorted-fixes' into 'master'
Style fix related to the previous merge.

See merge request core-developers/forge!504
2018-04-30 17:00:21 +00:00
Agetian
f8beba2815 - Style fix related to the previous merge. 2018-04-30 19:59:36 +03:00
Agetian
8c325d8264 - Style update. 2018-04-30 09:40:53 +03:00
Agetian
9aec30e0dc - FogAi update from Seravy (updated for style consistency). 2018-04-30 09:36:31 +03:00
Agetian
5049b7885a - FogAi update from Seravy (updated for style consistency). 2018-04-30 09:32:24 +03:00
Michael Kamensky
3eead9da4e Merge branch 'saga2' into 'master'
Saga: use better state based action

See merge request core-developers/forge!502
2018-04-30 06:24:17 +00:00
Agetian
9a206ff338 - Some style and logic fixes. 2018-04-30 08:49:20 +03:00
Agetian
4f487f3009 Merge branch 'FlickeringWard' of git.cardforge.org:Seravy/forge into seravy-ai-fixes 2018-04-30 08:36:46 +03:00
Michael Kamensky
d7a55e4c7d Merge branch 'ProtectionSelf' into 'master'
AI will use non-targeted Protection abilities.

See merge request core-developers/forge!245
2018-04-30 05:32:22 +00:00
Hanmac
cc654b56a1 GameAction: add canBeSacrificed check, no need to do the other checks later 2018-04-30 07:17:11 +02:00
Hanmac
66ac3ee63c WA: fixed Saga abilities 2018-04-30 06:51:01 +02:00
Meerkov
e421a32475 Style fixes for discussion.
(cherry picked from commit 3ae95447a2a91a36bd1772649592ef72ffb344f8)
2018-04-30 02:07:25 +00:00
Sol
1acb0e9e95 Merge branch 'dev-mode' into 'master'
Dev Mode: added a way to remove counters from permanent.

See merge request core-developers/forge!501
2018-04-30 00:17:43 +00:00
Sol
ab2d284dee Merge branch 'temp_dev5' into 'master'
Remove unusual "reproducable" attack declaration from AI

See merge request core-developers/forge!498
2018-04-30 00:16:30 +00:00
Michael Kamensky
f72c5aa59c Merge branch 'temp_dev4' into 'master'
Android updates

See merge request core-developers/forge!491
2018-04-29 20:16:28 +00:00
Hanmac
910dc4fb26 Saga: use better state based action: it only cares about chapter abilities, and get the final chapter nr from the triggers 2018-04-29 20:59:16 +02:00
Agetian
4bf54c721d - Comment fix. 2018-04-29 20:55:00 +03:00
Agetian
b482964373 - Sub counter: only list counters that are present on the card. 2018-04-29 20:19:11 +03:00
Agetian
01017efab6 - Dev Mode: added a way to remove counters from permanent. 2018-04-29 18:51:46 +03:00
Michael Kamensky
49558ca39d Merge branch 'outcomes-show-names-in-1v1' into 'master'
When playing non-team matches, display Player name as winner in Game Recap screen

See merge request core-developers/forge!500
2018-04-29 04:52:46 +00:00
Chris H
758dbb689a When playing non-team matches, display Player name as winner in Game Recap screen 2018-04-28 23:22:23 -04:00
Meerkov
536f9f4409 Proposal to fix Android warnings.
(cherry picked from commit eb65bd4a17d7c750cc2eb1a44cbbc309a7ada068)
2018-04-28 17:28:16 +00:00
Meerkov
c6cab341ed Organize some imports.
(cherry picked from commit 70cf1ad3d0a6449d67b8ed84c9ebf0da4fd13547)
2018-04-28 17:01:54 +00:00
Meerkov
792fdad4be Tiny Update: Removes dead code.
Just removes some code that does nothing.


(cherry picked from commit b462c41dff99f0173b7e228f826ca0bf0d8103cb)
2018-04-28 17:01:42 +00:00
Meerkov
bd097888a3 Allows running tests using a seeded RNG.
Running the same game twice now works! There may be issues I haven't found with certain AI behaviors around mechanics I didn't use in my tests.


(cherry picked from commit 194b47c1ad61c8f1efb6bce8af2bb10d1fa8f6c3)
2018-04-28 17:00:54 +00:00
Meerkov
eae90194cb AiAttackController no longer has reproducable attack thoughts.
I don't see why we would want this to be seeded with the same number. I think it's incorrect thinking so I'm removing it.


(cherry picked from commit 2881effd577abe9500b8fa3a5699067b0c3541f3)
2018-04-28 16:55:05 +00:00
Meerkov
0a717bb9be Fix more RNG.
(cherry picked from commit 61dd5661a728e08ae14911a3518cd1e10d574037)
2018-04-28 16:54:53 +00:00
Michael Kamensky
dc71046018 Merge branch 'assorted-fixes' into 'master'
AI: Only consider the "prevent all combat damage" pump KW useful in combat

See merge request core-developers/forge!497
2018-04-28 12:58:18 +00:00
Agetian
881572cba5 - AI: Only consider the "prevent all combat damage" pump KW useful when inside combat and blocking/blocked (e.g. Blinding Powder). 2018-04-28 15:57:47 +03:00
Hans Mackowiak
4a41c57e93 Merge branch 'assorted-fixes' into 'master'
(Experimental) Attempting to fix the interaction of text-changing abilities and keyworded effects

See merge request core-developers/forge!496
2018-04-28 12:43:04 +00:00
Agetian
084f45393e - Code tweak suggested by Hanmac. 2018-04-28 15:08:53 +03:00
Agetian
90a1a5f058 - (Experimental) Attempting to fix the interaction of text-changing abilities with keyworded effects that use overriding abilities (e.g. Fabricate and Artificial Evolution). 2018-04-28 13:56:53 +03:00
Michael Kamensky
e0486aff29 Merge branch 'assorted-fixes' into 'master'
Updating DrainMana ability description (mana pool reference removed).

See merge request core-developers/forge!494
2018-04-28 10:44:05 +00:00
Agetian
ce165f4234 - Updating DrainMana ability description. 2018-04-28 13:42:51 +03:00
Hans Mackowiak
4d73f954f8 Merge branch 'assorted-fixes' into 'master'
Removing references to mana pool in code (and updating the relevant code parts).

See merge request core-developers/forge!493
2018-04-28 10:40:03 +00:00
Agetian
445625ba1e - Removing references to mana pool in other parts of the code. 2018-04-28 13:34:16 +03:00
Michael Kamensky
e76e610b97 Merge branch 'assorted-fixes' into 'master'
Fixing a test with the new mana ability text in mind.

See merge request core-developers/forge!492
2018-04-28 10:22:50 +00:00
Agetian
8c50ddef2f - Fixing a test with the new mana ability text in mind. 2018-04-28 13:21:42 +03:00
Hans Mackowiak
d11c608bd0 Merge branch 'dom-oracle-mana-pool' into 'master'
Oracle non-functional update: removing "mana pool" from card texts.

See merge request core-developers/forge!487
2018-04-28 09:08:31 +00:00
Meerkov
9aac61e350 Undo XStream update, to support Android.
It's not clear why 1.4.10 was not working for Android, but it wasn't my goal to update specifically so rolling back that change.


(cherry picked from commit 43642e85a8d05d02d4c65c87eed8e51d5c1a3a56)
2018-04-28 08:42:48 +00:00
Meerkov
6436d6fa2a Delete silly android code.
I don't think this is necessary, it seems silly to me. I'm just deleting it.


(cherry picked from commit bf2c4b8cc6a9b2db84b81bc650140e9a29ae22b4)
2018-04-28 08:42:37 +00:00
Meerkov
8f68266585 Update Guava to 24.1
This updates Guava 16 to 24.1. Currently 16 is a 4 year old version, so there are a lot of bug fixes. The Android .pom has 24.1-android instead of 24.1-jre, for compatability reasons.

I did not test the android build, however the desktop build works.

Also resovles two warnings and drops a "TODO" on a comment that was highlighting a possible bug but wasn't easily marked to discover later.


(cherry picked from commit be578af80467c1850d9dcec0af6fe18840fff7e9)
2018-04-28 08:42:22 +00:00
Michael Kamensky
38376b3978 Merge branch 'assorted-fixes' into 'master'
Making transform cards work as plane/region art in Planar Conquest

See merge request core-developers/forge!488
2018-04-27 19:17:01 +00:00
Agetian
7d5b0be5ca - Removed an unused variable. 2018-04-27 13:48:00 +03:00
Agetian
98bc1fb01a - Somewhat better implementation for getArt in ConquestRegion. 2018-04-27 10:53:06 +03:00
Agetian
aa11704caa - Correction for Ixalan plane definition. 2018-04-27 10:52:03 +03:00
Agetian
bc701fecb3 - Making transform cards work as plane/region art in Planar Conquest (mobile Forge).
- Currently will show the cards untransformed on the plane selection screen and transformed on the multiverse/region screen.
2018-04-27 10:46:48 +03:00
Agetian
e8462e8291 - Oracle non-functional update: removing "mana pool" from card texts. Pass one. 2018-04-27 08:42:30 +03:00
swordshine
d74c6a2e21 Merge branch 'dom-pw-decks' into 'master'
Deck descriptions

See merge request core-developers/forge!486
2018-04-27 02:39:44 +00:00
Rob Schnautz
bddd94aec0 back matter, premium Sunblast Angel 2018-04-26 21:49:25 +00:00
Rob Schnautz
665d07cd56 back matter, premium Goliath Sphinx 2018-04-26 21:44:41 +00:00
Rob Schnautz
7375ad8a5d premium sphinx of uthuun 2018-04-26 21:41:41 +00:00
Rob Schnautz
581461f0e3 back matter 2018-04-26 21:39:11 +00:00
Rob Schnautz
3dc3da5932 back matter 2018-04-26 21:21:56 +00:00
Sol
0464601dc5 Merge branch 'dom-fixes' into 'master'
DOM fixes

See merge request core-developers/forge!485
2018-04-26 21:10:52 +00:00
Rob Schnautz
3f31bf85fb back matter 2018-04-26 21:10:31 +00:00
Chris H
7c4429da46 DOM fixes 2018-04-26 17:07:40 -04:00
Rob Schnautz
b73efba916 back matter 2018-04-26 21:06:53 +00:00
Rob Schnautz
58cbcee5ba back matter 2018-04-26 21:01:50 +00:00
Rob Schnautz
ab8b71b152 back matter 2018-04-26 20:56:34 +00:00
Rob Schnautz
ca71a997dd back matter 2018-04-26 20:55:30 +00:00
Sol
ce6ddd2f91 Merge branch 'fix_shivan_fire' into 'master'
Fix Shivan FIre

See merge request core-developers/forge!484
2018-04-26 20:46:01 +00:00
Chris H
c938f8e731 Fix Shivan FIre 2018-04-26 16:40:03 -04:00
Michael Kamensky
7df7703c50 Merge branch 'assorted-fixes' into 'master'
- Fix the implementation of Exalted.

See merge request core-developers/forge!483
2018-04-26 04:45:21 +00:00
Agetian
33bc3557e5 - Fix the implementation of Exalted. 2018-04-26 07:44:38 +03:00
Michael Kamensky
cf2bc158a0 Merge branch 'assorted-fixes' into 'master'
- Preparing Forge for Android publish 1.6.9.005 [hotfix].

See merge request core-developers/forge!482
2018-04-26 04:36:26 +00:00
Agetian
e8d0fe07f4 - Preparing Forge for Android publish 1.6.9.005 [hotfix]. 2018-04-26 07:36:04 +03:00
Michael Kamensky
a4fa7cb5ee Merge branch 'assorted-fixes' into 'master'
Fixed Exalted and possibly other triggers with overwriting ability crashing the AI

See merge request core-developers/forge!481
2018-04-26 04:35:25 +00:00
Agetian
6ea52c93fe - Fixed Exalted and possibly other triggers with overwriting ability crashing the trigger checking code in getPossibleNonCombatDamage. 2018-04-26 07:34:14 +03:00
Michael Kamensky
b28887e91c Merge branch 'sortFix' into 'master'
D'Avenant before Danitha

See merge request core-developers/forge!480
2018-04-26 04:18:24 +00:00
Rob Schnautz
263b906aec D'Avenant before Danitha 2018-04-26 02:44:39 +00:00
austinio7116
acef14aadc Merge branch 'fix_outcome_memory_leak' into 'master'
Memory leak within a match

See merge request core-developers/forge!458
2018-04-25 22:35:02 +00:00
Michael Kamensky
055ebac496 Merge branch 'assorted-fixes' into 'master'
- NPE prevention related to guaranteed cards in boosters (for Quest Mode).

See merge request core-developers/forge!479
2018-04-25 04:27:33 +00:00
Agetian
285370fe35 - NPE prevention related to guaranteed cards in boosters (for Quest Mode). 2018-04-25 07:26:54 +03:00
Michael Kamensky
23af3953e6 Merge branch 'assorted-fixes' into 'master'
Preparing Forge for Android publish 1.6.9.004 [AI hotfix].

See merge request core-developers/forge!478
2018-04-25 04:18:18 +00:00
Agetian
83665d0992 - Preparing Forge for Android publish 1.6.9.004 [AI hotfix]. 2018-04-25 07:17:39 +03:00
Michael Kamensky
c6dcb9db2b Merge branch 'assorted-fixes' into 'master'
AI: fixed a logic error in DealDamage, somewhat better prediction of noncombat damage for assault attacks

See merge request core-developers/forge!475
2018-04-25 04:14:28 +00:00
Michael Kamensky
b3205d43db Merge branch 'master' into 'master'
Tweaked the updated Drudge Sentinel.

See merge request core-developers/forge!477
2018-04-24 18:05:09 +00:00
Agetian
4feefd404b - Tweaked the updated Drudge Sentinel. 2018-04-24 21:04:23 +03:00
Michael Kamensky
b202642793 Merge branch 'ai_drudge_sentinel' into 'master'
Flip Drudge Sentinel script to improve the AI handling

See merge request core-developers/forge!476
2018-04-24 18:03:20 +00:00
Chris H
ea3e91483a Flip Drudge Sentinel script to improve the AI handling 2018-04-24 10:27:34 -04:00
Agetian
4ae6d9030a - Fixed the AI not targeting creatures with DealDamage anymore (e.g. Walking Ballista). 2018-04-24 12:01:13 +03:00
Agetian
c97353cb3d - Filter out permanent cards in the AI's hand when looking for possible non-combat damage in combat (the logic is too indirect and will result in the AI making mistakes anyway, e.g. due to inability to pay for both the permanent and the ability and guarantee a win). 2018-04-24 11:53:57 +03:00
Agetian
71b2f2fa26 - NPE prevention. 2018-04-24 10:11:43 +03:00
Agetian
3c8915b002 - AI should not count permanent cards in hand which it can't cast at the moment as possible sources of noncombat damage when predicting an assault attack.
- Some AI prediction for LoseLife triggers when predicting noncombat damage
2018-04-24 10:07:59 +03:00
Chris H
a54a9beb87 Fix Quest WinLose checking wrong Player type against each other 2018-04-23 20:30:09 -04:00
Michael Kamensky
0ad2dcb234 Merge branch 'assorted-fixes' into 'master'
Do not add guaranteed legendary cards to Dominaria boosters from PW decks.

See merge request core-developers/forge!474
2018-04-23 19:31:16 +00:00
Agetian
2b34c68057 - Do not add guaranteed legendary cards to Dominaria boosters from the ones that are reserved for Planeswalker decks. 2018-04-23 22:27:21 +03:00
Chris H
f7a739a023 Fix incompatibilities with Android API 2018-04-23 15:21:41 -04:00
Michael Kamensky
3d38b029a1 Merge branch 'fix-suspend' into 'master'
Attempting to fix issues with Suspend not working in different ways

See merge request core-developers/forge!473
2018-04-23 19:05:41 +00:00
Agetian
9ffad0437a - Updating the card cache directly is no longer needed after the fix in CardFactoryUtil is complete. 2018-04-23 22:04:15 +03:00
Agetian
844c083ee4 - Attempting to fix issues with Suspend not working in different ways (not being able to cast a spell with suspend, not being able to resuspend a card returned from graveyard). 2018-04-23 20:35:44 +03:00
Michael Kamensky
813b20a55c Merge branch 'master' into 'master'
Preparing Forge for Android publish 1.6.9.003 [hotfix].

See merge request core-developers/forge!472
2018-04-23 06:58:18 +00:00
Agetian
a8277f5e13 - Preparing Forge for Android publish 1.6.9.003 [hotfix]. 2018-04-23 09:56:48 +03:00
Hans Mackowiak
922c7e148c Merge branch 'prevent-repleffect-fix' into 'master'
Fixing cards with a damage prevention replacement effect which did not have ActiveZones set.

See merge request core-developers/forge!471
2018-04-23 06:55:01 +00:00
Agetian
6f421e688a - Fixing cards with a damage prevention replacement effect which did not have ActiveZones set. 2018-04-23 09:38:51 +03:00
austinio7116
e5db8ff8b3 Merge branch 'assorted-fixes' into 'master'
Adding Rat Colony to card limit exceptions, fixing Goblin Chainwhirler.

Closes #527

See merge request core-developers/forge!470
2018-04-23 06:36:40 +00:00
Agetian
75d6ea6537 - Unifying the list for card exceptions in DeckFormat, as suggested and coded by Austinio. 2018-04-23 09:21:55 +03:00
Agetian
4a8d4346be - Fixed Goblin Chainwhirler not having First Strike. 2018-04-23 07:36:19 +03:00
Agetian
1522baa97d - Adding Rat Colony to card limit exceptions in several places. 2018-04-23 07:31:39 +03:00
Michael Kamensky
a45f068a39 Merge branch 'mobile-release' into 'master'
Preparing Forge for Android publish 1.6.9.002 [bugfixes].

See merge request core-developers/forge!469
2018-04-23 04:11:30 +00:00
Agetian
ec266d6d9b - Preparing Forge for Android publish 1.6.9.002 [bugfixes]. 2018-04-23 06:49:21 +03:00
Sol
8c64bd64cf Merge branch 'puzzle-fixes' into 'master'
PS_RIX3: remove summoning sickness from thopter tokens

Closes #528

See merge request core-developers/forge!468
2018-04-23 01:07:09 +00:00
Jamin W. Collins
4dadeaa083 PS_RIX3: remove summoning sickness from thopter tokens
Fixes: core-developers/forge#528

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-04-22 18:28:45 -06:00
Chris H
ade17c5eaf Fix Memory leak within a match 2018-04-22 15:18:22 -04:00
Sol
e44686b075 Merge branch 'revert-75d39721' into 'master'
Revert "Delete Promo set for Gatherer.txt"

See merge request core-developers/forge!467
2018-04-22 18:27:53 +00:00
Rob Schnautz
55d97404f2 Revert "Delete Promo set for Gatherer.txt"
This reverts commit 75d3972196
2018-04-22 18:22:36 +00:00
Michael Kamensky
e60d5173f4 Merge branch 'assorted-fixes' into 'master'
Fixed references to the no longer existing set MBP.

Closes #525

See merge request core-developers/forge!466
2018-04-22 17:47:47 +00:00
Agetian
30d4bb923b - Fixed references to the no longer existing set MBP. 2018-04-22 20:46:16 +03:00
Hans Mackowiak
659a2e5a55 Merge branch 'assorted-fixes' into 'master'
Attempting to fix inverse logic in CardType#setCreatureTypes.

See merge request core-developers/forge!464
2018-04-22 17:26:12 +00:00
Agetian
0b128cc4ab - Attempting to fix inverse logic in CardType#setCreatureTypes. 2018-04-22 20:09:03 +03:00
Michael Kamensky
a4c1fea08b Merge branch 'domimages' into 'master'
Added challenger deck precons and fixed/sorted DOM token image paths

See merge request core-developers/forge!463
2018-04-22 14:34:28 +00:00
austinio7116
8a50cb9ea3 Fixed name of second sun control image for consistency 2018-04-22 09:13:00 +01:00
austinio7116
62a6839f84 Adding challenger deck precon images 2018-04-22 08:51:04 +01:00
austinio7116
023af864b6 ordered and renamed DOM token images 2018-04-22 08:17:13 +01:00
Michael Kamensky
d0e167d93c Merge branch 'domimages' into 'master'
Added blockdata for fatpacks and booster boxes and token image list for DOM

See merge request core-developers/forge!460
2018-04-22 06:47:24 +00:00
austinio7116
33c6e01d34 Added DOM token list images 2018-04-22 07:22:21 +01:00
austinio7116
4312791cf6 Added blockdata for fatpacks and booster boxes for DOM 2018-04-22 07:10:08 +01:00
Sol
ad1c2b6f0b Merge branch 'domimages' into 'master'
Added DOM quest images

See merge request core-developers/forge!457
2018-04-22 03:24:56 +00:00
Michael Kamensky
78525a830b Merge branch 'assorted-fixes' into 'master'
Added NPE prevention for situations where AI evaluation for targeting cards with Shroud in MoJhoSto crashes the game.

See merge request core-developers/forge!455
2018-04-21 20:16:58 +00:00
Michael Kamensky
b02f8e5dc7 Merge branch 'mojhosto-ai-choices' into 'master'
- MoJhoSto: some more profile-based configurability for AI strategy.

See merge request core-developers/forge!456
2018-04-21 20:16:20 +00:00
Agetian
28998e97ab - MoJhoSto: some more profile-based configurability for AI strategy. 2018-04-21 23:15:26 +03:00
austinio7116
8c3fefd3ff Added DOM quest images 2018-04-21 21:09:41 +01:00
Agetian
819301c7d8 - FIXME: Added NPE prevention for situations where targeting cards with Shroud would be tested, but the SA activator would be null at this point (seems to happen for the AI in MoJhoSto matches; probably needs a more thorough long-term solution). 2018-04-21 19:58:03 +03:00
Michael Kamensky
76df4d2412 Merge branch 'master' into 'master'
Marking Izzet Chemister as RemAIDeck, the AI sacs it immediately

See merge request core-developers/forge!454
2018-04-21 15:34:58 +00:00
Agetian
6e95a36fb2 - Marking Izzet Chemister as RemAIDeck, the AI has no logic for it and immediately sacrifices it for no reason. 2018-04-21 18:34:20 +03:00
Michael Kamensky
a6e81e5d09 Merge branch 'dominaria-guaranteed' into 'master'
Some refactoring in BoosterGenerator related to guaranteed cards.

See merge request core-developers/forge!453
2018-04-21 12:24:45 +00:00
Agetian
b7e48ac2cc - Some refactoring in BoosterGenerator related to guaranteed cards. 2018-04-21 15:23:46 +03:00
swordshine
35dc1bf028 Merge branch 'traxisfix' into 'master'
Deck Generator Improvements

See merge request core-developers/forge!452
2018-04-21 10:17:53 +00:00
swordshine
816ff7a8eb Merge branch 'dominaria-guaranteed' into 'master'
Dominaria: booster packs must have a guaranteed Legendary Creature

See merge request core-developers/forge!451
2018-04-21 10:17:33 +00:00
Agetian
2b41c97355 - Dominaria: booster packs must have a guaranteed Legendary Creature 2018-04-21 08:34:25 +03:00
austinio7116
131def83b9 removed commented out old code 2018-04-21 06:29:43 +01:00
Michael Kamensky
e40211ad49 Merge branch 'master' into 'master'
- Preparing Forge for Android publish 1.6.9.001 [incremental].

See merge request core-developers/forge!450
2018-04-21 03:37:40 +00:00
Agetian
30f8e8b16c - Preparing Forge for Android publish 1.6.9.001 [incremental]. 2018-04-21 06:36:46 +03:00
Blacksmith
5a9547e79b Clear out release files in preparation for next release 2018-04-21 01:23:46 +00:00
Blacksmith
0d375ecd93 [maven-release-plugin] prepare for next development iteration 2018-04-21 01:18:35 +00:00
Blacksmith
970f684345 [maven-release-plugin] prepare release forge-1.6.9 2018-04-21 01:18:32 +00:00
Blacksmith
992372913e Update README.txt for release 2018-04-21 01:16:51 +00:00
Sol
28f328b0bc Merge branch 'fix_editions_tracking' into 'master'
Simple fix for EditionTracking with new format style

See merge request core-developers/forge!449
2018-04-21 01:16:11 +00:00
Chris H
2196551126 Simple fix for EditionTracking with new format style 2018-04-20 21:10:21 -04:00
austinio7116
84653d7aae Deactivated deck generation logs 2018-04-21 00:00:40 +01:00
austinio7116
7cb11e5191 Deck generation card quality and mana curve improvements 2018-04-20 23:59:19 +01:00
Sol
916b026b12 Merge branch 'release_files_update' into 'master'
Updating release files for release

See merge request core-developers/forge!448
2018-04-20 22:21:51 +00:00
Chris H
c6406e9f16 Updating release files for release 2018-04-20 18:18:35 -04:00
Michael Kamensky
2b14e25935 Merge branch 'master' into 'master'
Revert a change in GameSimulator to make it work and prevent tests from failing

See merge request core-developers/forge!447
2018-04-20 20:03:10 +00:00
Agetian
2f8fd5c359 - Revert a change in GameSimulator to make it work and prevent tests from failing (may require further attention later). 2018-04-20 23:02:38 +03:00
Michael Kamensky
f342fe3351 Merge branch 'traxisfix' into 'master'
Deck generation fix and performance issue

See merge request core-developers/forge!446
2018-04-20 18:52:07 +00:00
austinio7116
a60f5d1660 Fixed deck generation performance issue 2018-04-20 19:13:35 +01:00
austinio7116
e6c4f6c703 Generic mana fix for deck generation 2018-04-20 19:11:52 +01:00
austinio7116
51bf8a5286 Fixed deck generation performance issue 2018-04-20 18:23:59 +01:00
austinio7116
7df721a863 Fix for colourless brawl commanders 2018-04-20 18:04:54 +01:00
swordshine
ee7a3e934e Merge branch 'fixes-hanmac' into 'master'
Fixes

Closes #430

See merge request core-developers/forge!445
2018-04-20 12:04:53 +00:00
Hanmac
04254fcb00 GameSimulator: use canPlay & resolve for PlayLandAbility 2018-04-20 13:06:13 +02:00
Hanmac
6911906c4f CardThemedDeckBuilder: some final fixes 2018-04-20 13:04:12 +02:00
Hanmac
47cf2040f6 PlayerControllerHuman: chooseCardName need to respect face name 2018-04-20 13:03:35 +02:00
Hanmac
c47da9acdd PlayEffect: don't cast to CardCollection 2018-04-20 13:01:29 +02:00
Hanmac
a11d5a0fa5 CardDb: fixed other Cardface 2018-04-20 13:00:44 +02:00
Michael Kamensky
3c2bb6dfe2 Merge branch 'mojhosto-ai-choices' into 'master'
Moved an AI option in Experimental AI profile.

See merge request core-developers/forge!444
2018-04-20 06:40:23 +00:00
Agetian
d3be1a5bec - Moved an AI option in Experimental AI profile. 2018-04-20 09:39:49 +03:00
Michael Kamensky
61792e7875 Merge branch 'mojhosto-ai-choices' into 'master'
MoJhoSto: added an option to control the number of lands at which the AI considers Jhoira.

See merge request core-developers/forge!443
2018-04-20 06:19:17 +00:00
Agetian
c470e0cdcb - MoJhoSto: added an option to control the number of lands at which the AI begins to consider activating Jhoira. 2018-04-20 09:18:03 +03:00
austinio7116
342b84e7ad Merge branch 'deckgendata' into 'master'
Updated deck generation models

See merge request core-developers/forge!442
2018-04-20 06:02:45 +00:00
austinio7116
d98a0be80a Removed duplicate format file 2018-04-20 06:48:09 +01:00
austinio7116
a43907fe14 Reversed date ordering when checking deck formats so that when only one of each subtype is shown the earliest valid format is used 2018-04-20 06:46:26 +01:00
Michael Kamensky
98cc18b12f Merge branch 'mojhosto-ai-choices' into 'master'
MoJhoSto AI: don't activate Jhoira too early since there are generally few good potential targets.

See merge request core-developers/forge!441
2018-04-20 05:17:11 +00:00
Agetian
226b0bee68 - MoJhoSto: don't activate Jhoira too early since there are generally few good potential targets. 2018-04-20 08:16:07 +03:00
Michael Kamensky
6189e0cb74 Merge branch 'mojhosto-ai-choices' into 'master'
MoJhoSto: somewhat more varied AI strategy.

See merge request core-developers/forge!438
2018-04-20 04:43:26 +00:00
Agetian
23470629bd Merge remote-tracking branch 'origin/mojhosto-ai-choices' into mojhosto-ai-choices 2018-04-20 07:35:56 +03:00
Agetian
d073fb9417 - Some improvements to the Jhoira AI algorithm in MoJhoSto: don't rely on an AI logic (since it's mode-specific).
- Fixed a crash related to the AI playing MoJhoSto.
2018-04-20 07:35:14 +03:00
Agetian
d623119eac - MoJhoSto: somewhat more varied AI strategy when deciding between Momir and Jhoira. Also, don't spam Jhoira instant copying ability all the time. 2018-04-20 07:35:14 +03:00
Michael Kamensky
5844de9b3c Merge branch 'assorted-fixes' into 'master'
Fixed the AI hanging the game in AF ChooseSource in corner cases

See merge request core-developers/forge!440
2018-04-20 04:33:41 +00:00
Agetian
a1a7390d80 - Fixed the AI hanging the game when trying to choose a source in absence of a creature in combat on the opponent's battlefield. 2018-04-20 07:13:25 +03:00
Agetian
dd2bc70f47 - Some improvements to the Jhoira AI algorithm in MoJhoSto: don't rely on an AI logic (since it's mode-specific).
- Fixed a crash related to the AI playing MoJhoSto.
2018-04-20 06:46:13 +03:00
maustin
a6acf6527c Merge branch 'master' into historicformats 2018-04-19 22:45:03 +01:00
austinio7116
5310a0d6b7 Deck generation data files updated 2018-04-19 22:37:52 +01:00
maustin
a2b931571e Merge remote-tracking branch 'Core/master' into coremaster 2018-04-19 22:36:04 +01:00
Agetian
da9543e426 - MoJhoSto: somewhat more varied AI strategy when deciding between Momir and Jhoira. Also, don't spam Jhoira instant copying ability all the time. 2018-04-19 23:06:05 +03:00
austinio7116
2cfd036466 Standard formats checked and effective dates added from RIX back to Avacyn Restored
Corrected effective date parsing to match format files and reversed date order so latest formats appear first
2018-04-19 20:54:11 +01:00
Michael Kamensky
13b058ed1b Merge branch 'brawlfixes' into 'master'
Brawl and Tiny Leader Android fixes

See merge request core-developers/forge!437
2018-04-19 18:01:38 +00:00
maustin
0c8acd32d8 Merge branch 'master' into historicformats 2018-04-19 18:51:39 +01:00
maustin
fc62a790cd Merge remote-tracking branch 'Core/master' into coremaster 2018-04-19 18:50:23 +01:00
maustin
eb0724954b Merge branch 'brawlfixes' into historicformats 2018-04-19 18:21:36 +01:00
maustin
0168a20e7e Use later reprints in card-themed generated decks 2018-04-19 18:21:00 +01:00
maustin
b394bd3eac Merge branch 'brawlfixes' into historicformats 2018-04-19 17:11:53 +01:00
austinio7116
d0b4fa9328 Use later reprints in card-themed generated decks 2018-04-19 17:11:35 +01:00
Michael Kamensky
3b50fde76e Merge branch 'master' into 'master'
Brawl deck generation fixes

See merge request core-developers/forge!436
2018-04-19 14:45:29 +00:00
maustin
a7ab8deac4 Merge branch 'brawlfixes' into historicformats 2018-04-19 13:14:36 +01:00
maustin
a2aa269851 Merge branch 'brawlfixes' 2018-04-19 12:59:51 +01:00
maustin
d31e1a65a8 Merge branch 'brawlfixes' into coremaster 2018-04-19 12:58:49 +01:00
austinio7116
76cad4ae7e Fixed repeated cards in brawl and improved quality of randomly added cards 2018-04-19 12:58:15 +01:00
swordshine
cda39c2a05 Merge branch 'update-formats' into 'master'
Adding Dominaria to Standard and Modern format definitions.

See merge request core-developers/forge!434
2018-04-19 10:58:51 +00:00
Michael Kamensky
6cc4276288 Merge branch 'master' into 'master'
- Fixed some cards

See merge request core-developers/forge!435
2018-04-19 10:31:17 +00:00
swordshine
c829915948 - Update more scripts 2018-04-19 18:25:18 +08:00
swordshine
1dcb56d223 - Fixed Gift of Growth 2018-04-19 18:11:25 +08:00
swordshine
90a2e0a9e1 - Fixed some cards 2018-04-19 18:03:47 +08:00
Agetian
65aad0b09a - Adding Dominaria to Standard and Modern format definitions. 2018-04-19 11:21:06 +03:00
swordshine
b5a3ac6163 Merge branch 'ai-torgaar' into 'master'
Basic AI for Torgaar (currently won't sac anything as a part of cost payment)

See merge request core-developers/forge!425
2018-04-19 07:06:56 +00:00
swordshine
ed7ed059a2 Merge branch 'dominaria-achievements' into 'master'
Dominaria achievements suggested by Marek

See merge request core-developers/forge!433
2018-04-19 07:04:31 +00:00
Agetian
d0a279e04e - Adding Dominaria achievements suggested by Marek (with minor modifications for style consistency and to fit into the relevant boxes). 2018-04-19 09:39:40 +03:00
maustin
4303e09fe3 Merge branch 'master' into historicformats 2018-04-19 06:52:37 +01:00
maustin
ac9b04de67 Merge branch 'coremaster' 2018-04-19 06:52:13 +01:00
swordshine
e79256e836 Merge branch 'master' into 'master'
Fixed some cards

See merge request core-developers/forge!432
2018-04-19 02:39:43 +00:00
swordshine
1dcfcc3531 - Healing Grace can target planeswalker 2018-04-19 10:39:13 +08:00
swordshine
11b1d29df6 Merge branch 'master' into 'master'
Fixed some cards

See merge request core-developers/forge!431
2018-04-19 00:53:44 +00:00
swordshine
ad30caa7c8 - Fixed some cards by stormcat 2018-04-19 08:52:50 +08:00
maustin
53a03b94d4 Merge branch 'master' into historicformats
# Conflicts:
#	forge-game/src/main/java/forge/game/GameFormat.java
2018-04-18 23:00:56 +01:00
austinio7116
77da5c973b Fixed repeated cards in brawl 2018-04-18 22:54:52 +01:00
maustin
622f64ad29 Merge remote-tracking branch 'Core/master'
# Conflicts:
#	forge-ai/src/main/java/forge/ai/ability/DigAi.java
2018-04-18 22:42:22 +01:00
Michael Kamensky
0700e7a736 Merge branch 'master' into 'master'
- Updating CHANGES.txt.

See merge request core-developers/forge!429
2018-04-18 18:23:51 +00:00
Agetian
4e1e06c8f7 - Updating CHANGES.txt. 2018-04-18 21:23:26 +03:00
Michael Kamensky
3bb69e356c Merge branch 'healinggrace' into 'master'
ReplaceDamage: new Effect for reducing Damage inside ReplacementEffect

Closes #268

See merge request core-developers/forge!427
2018-04-18 18:20:17 +00:00
Agetian
9993366ade - Experimental: DigAi prefers to give lands to the opponent instead of functional cards e.g. for Karn, Scion of Urza. 2018-04-18 19:15:45 +01:00
Michael Kamensky
e5d436c295 Merge branch 'master' into 'master'
Memory Leaks, Unused code, compiler warnings, etc.

See merge request core-developers/forge!423
2018-04-18 15:40:37 +00:00
Hanmac
211157c0d0 ReplaceDamage: new Effect for reducing Damage inside ReplacementEffect 2018-04-18 17:24:59 +02:00
Agetian
f83e097769 Merge remote-tracking branch 'origin/ai-torgaar' into ai-torgaar
# Conflicts:
#	forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
2018-04-18 17:18:49 +03:00
Agetian
dcacdf65ce - Reduced the life threshold for healing self with Torgaar. 2018-04-18 17:14:55 +03:00
Agetian
4b61620538 - Somewhat better LifeSetAi logic for Torgaar which accounts for possible different starting life. 2018-04-18 17:11:02 +03:00
Agetian
8b0a5e886e - Basic AI for Torgaar (currently won't sac anything as a part of cost payment)
- Fixed references in the script for Torgaar.
2018-04-18 17:11:02 +03:00
Michael Kamensky
1045204fb7 Merge branch 'ai-give-lands-to-opp' into 'master'
DigAi prefers to choose lands for opp's Karn, Scion of Urza +1 and other similar abilities.

Closes #508

See merge request core-developers/forge!413
2018-04-18 14:09:15 +00:00
Agetian
7e1226bc3d - Refactored chooseSingleCard in DigAi. 2018-04-18 17:08:12 +03:00
Agetian
b8deb244ca Merge remote-tracking branch 'origin/ai-torgaar' into ai-torgaar 2018-04-18 09:53:42 +03:00
Agetian
6a47d10d7b - Somewhat better LifeSetAi logic for Torgaar which accounts for possible different starting life. 2018-04-18 09:53:24 +03:00
Agetian
694ac39e89 - Basic AI for Torgaar (currently won't sac anything as a part of cost payment)
- Fixed references in the script for Torgaar.
2018-04-18 09:53:24 +03:00
Michael Kamensky
a295f34094 Merge branch 'assorted-fixes' into 'master'
Fixed references in Torgaar, Famine Incarnate.

See merge request core-developers/forge!426
2018-04-18 06:52:07 +00:00
Agetian
6bb66646b8 - Fixed references in Torgaar, Famine Incarnate. 2018-04-18 09:49:53 +03:00
Agetian
6c0112262e - Somewhat better LifeSetAi logic for Torgaar which accounts for possible different starting life. 2018-04-18 09:45:00 +03:00
Agetian
4bae4c7491 - Basic AI for Torgaar (currently won't sac anything as a part of cost payment)
- Fixed references in the script for Torgaar.
2018-04-18 09:25:58 +03:00
Michael Kamensky
e186356591 Merge branch 'torgaar' into 'master'
Add Torgaar (DOM)

See merge request core-developers/forge!424
2018-04-18 04:23:00 +00:00
Chris H
f3afaf9c51 Add Torgaar (DOM) 2018-04-17 23:08:53 -04:00
Meerkov
6efd631fd8 Declares a serialVersionUID.
Lots of classes here are serializable, but warn due to missing UID. Serializables are supposed to have a UID for version compatability. Fixes a bunch of compiler warnings.

Also adds in "default:" branch to a few switches which were warning due to missing cases.
2018-04-17 16:48:18 -07:00
Meerkov
0aea9ce153 Fix imports.
Woops, commited too fast. Adding imports for MyRandom.
2018-04-17 16:28:27 -07:00
Meerkov
4925db1b66 Remove Math.Random in favor of MyRandom.
This is a continuation of the RNG updates. Math.Random is an insecure PRNG which is actually different than java.util.Random as well.
2018-04-17 16:25:43 -07:00
Meerkov
72baafe4f2 Deletes 5 year old, unused JQuery code.
It seems like the developer planned to do something with it, but never did. Removing it seems like the only reasonable action.
2018-04-17 16:14:35 -07:00
Meerkov
64b98a84f5 Fix memory leak in FServerManager. 2018-04-17 16:11:25 -07:00
Meerkov
db3e4d9e83 Fixes ~50 compiler warnings for unused libraries. 2018-04-17 16:06:00 -07:00
Meerkov
338ba5afdc Fixes some compile errors in Eclipse due to empty folders. 2018-04-17 15:50:53 -07:00
maustin
f2ae137947 Merge branch 'master' into historicformats
# Conflicts:
#	forge-game/src/main/java/forge/game/GameFormat.java
2018-04-17 21:29:03 +01:00
maustin
c035636e94 Merge branch 'ai-give-lands-to-opp' of https://git.cardforge.org/Agetian/forge 2018-04-17 20:52:41 +01:00
Michael Kamensky
f51b80739a Merge branch 'master' into 'master'
Re-merge request for RNG Changes etc

See merge request core-developers/forge!422
2018-04-17 15:35:05 +00:00
Michael Kamensky
d1eea7e7ba Merge branch 'mayPlayLand' into 'master'
May play land

See merge request core-developers/forge!415
2018-04-17 14:54:04 +00:00
Michael Kamensky
a225cf0f92 Merge branch 'frostwielder' into 'master'
cards: Replace Moved that does check on damaged by need to use ValidLKI

Closes #406

See merge request core-developers/forge!421
2018-04-17 14:41:14 +00:00
Hanmac
ae5f85352e cards: Replace Moved that does check on damaged by need to use ValidLKI
Closes #406
2018-04-17 15:44:32 +02:00
Michael Kamensky
ffc414062b Merge branch 'patch-AnimateAllAI-mandatory' into 'master'
AnimateAllAi: fixed doTriggerAi for mandatory

See merge request core-developers/forge!420
2018-04-17 11:50:30 +00:00
Hans Mackowiak
da50071765 AnimateAllAi: fixed doTriggerAi for mandatory 2018-04-17 11:28:08 +00:00
Hanmac
3b9ea37907 Player: add playLandNoCheck to be used by LandAbility 2018-04-17 09:04:50 +02:00
Michael Kamensky
1a5710c564 Merge branch 'ai-better-pw-damage' into 'master'
Better ranking system for damaging planeswalkers directly

See merge request core-developers/forge!414
2018-04-17 07:01:47 +00:00
Agetian
9650014506 - Pay costs for ultimate SA shouldn't be null, but better safe than sorry. 2018-04-17 09:59:22 +03:00
Hanmac
eda7fee2c6 AiController: fixed MayPlay for Land 2018-04-17 08:07:11 +02:00
Hanmac
79c9c914e2 LandAbility: do extra ability class, so it can use mayPlay 2018-04-17 08:07:11 +02:00
Hanmac
190db26ebd MayPlay: fixed Bestow and MorphDown Effects 2018-04-17 08:07:11 +02:00
Meerkov
857f45f9ec Updates AI code to use MyRandom directly.
This removes java.util.Random from lots of AI code, futher making it unlikely that developers will accidentally use "new Random()" instead of MyRandom class. This will make running AI experiments easier, because there will be exactly one class that controls the randomness.
2018-04-16 21:31:52 -07:00
Meerkov
5c9a27c930 Update lots of code to use MyRandom directly.
Some parts of the code were using the normal random, instead of going through the MyRandom class. This makes it much harder to change the RNG method later. For example, you may want to change the RNG to always be seeded with the same number, for running AI experiments. This change makes that easier.
2018-04-16 21:26:12 -07:00
Meerkov
645d70e6ce Fixes inefficient shuffle algorithm.
Note that shuffling 13 times in a row is not necessary. Collections.shuffle will produce every possible shuffle with equal probability already.
2018-04-16 20:40:20 -07:00
Meerkov
130fc4ee18 Complete update to Java 8. 2018-04-16 20:38:20 -07:00
swordshine
ea32363144 Merge branch 'master' into 'master'
Updated the oracle text of Blackblade Reforged

See merge request core-developers/forge!418
2018-04-17 02:50:45 +00:00
swordshine
077eb106e3 Merge branch 'master' into 'master'
Updates the project for Java 8.

See merge request core-developers/forge!416
2018-04-17 02:48:14 +00:00
swordshine
3070dddf96 - Updated the oracle text of Blackblade Reforged 2018-04-17 10:45:30 +08:00
swordshine
708668df3f Merge branch 'migrate_upcoming' into 'master'
Migrate upcoming DOM files

See merge request core-developers/forge!417
2018-04-17 02:42:44 +00:00
Chris H
ea1770b1a7 Migrate upcoming DOM files 2018-04-16 21:32:59 -04:00
Meerkov
1cfb937490 Updates the project for Java 8.
Maps.newHashMap() was replaced with null in numerous locations. A null check is performed in the locations that read this value. This prevents unnecessary allocation of memory and fixes compiler errors.

"final" keyword added to RepeatEachEffect, which used local variables in non-local scope.
2018-04-16 18:26:15 -07:00
austinio7116
41e1997d73 Deactivate the choose format button if a quest world is selected 2018-04-16 22:00:55 +01:00
austinio7116
e359042766 New quest screen format selector now updates button text when a format is selected 2018-04-16 20:33:38 +01:00
Agetian
ae05e58a13 - Somewhat more explicit method name. 2018-04-16 22:18:33 +03:00
austinio7116
ad6f26f6a9 Separated the historic format selector UI for android from the filter class so that it can also be used to select any format for quest pools 2018-04-16 20:17:19 +01:00
Agetian
2c9b23fc6d - Better ranking system for damaging planeswalkers directly (accounts for loyalty and closeness to ultimate for now). 2018-04-16 21:31:49 +03:00
maustin
defaebd064 Merge branch 'master' into historicformats 2018-04-16 06:48:08 +01:00
Agetian
1fb559aedb - Experimental: DigAi prefers to give lands to the opponent instead of functional cards e.g. for Karn, Scion of Urza. 2018-04-16 07:36:44 +03:00
Michael Kamensky
29292956b6 Merge branch 'assorted-fixes' into 'master'
A better implementation of reckless attack prevention in AiAttackController.

See merge request core-developers/forge!412
2018-04-16 04:32:14 +00:00
Agetian
ffaf0eb8d5 - A better implementation of reckless attack prevention in AiAttackController. 2018-04-16 07:30:40 +03:00
swordshine
3344453253 Merge branch 'ai-careful-attack-effect' into 'master'
Cautiously configured AI should not chump attack recklessly with creatures that have AttackEffect/CombatEffect trying purely to trigger their effect.

See merge request core-developers/forge!411
2018-04-16 00:51:55 +00:00
swordshine
b60d0e057d Merge branch 'ai-haste-detection' into 'master'
Improve the AI Haste detection from Command zone and opponent's Battlefield

See merge request core-developers/forge!410
2018-04-16 00:51:09 +00:00
swordshine
9d0ffaddc5 Merge branch 'assorted-fixes' into 'master'
Improve the AI timing for Indestructible pump abilities.

Closes #507

See merge request core-developers/forge!409
2018-04-16 00:49:51 +00:00
austinio7116
e1ed1ae69c Grouped formats by historic subtype as well as type in android scroll selector 2018-04-15 19:53:30 +01:00
Agetian
1bb5f10fad - Better place for the defPower count. 2018-04-15 21:32:33 +03:00
Agetian
c0d65afde4 - Fixing an inadvertent comment change. 2018-04-15 21:12:58 +03:00
Agetian
e37ac89b88 - A somewhat better logic correction for attacking into dangerous blocks. 2018-04-15 21:11:05 +03:00
Agetian
cbd1ecec12 - AI should not attack recklessly with Kari Zev, Skyship Raider into certain death trying only to trigger its attack effect.
- AI SVar "DontChumpAttackForEffect" can probably be added to other cards with weak or temporary attack effects that are not worth wasting a card for.
2018-04-15 20:05:05 +03:00
Agetian
536dfb5f2d - Attempting to improve Haste detection for the AI such that the AI properly casts creatures in main 1 when there's an Avatar or Emblem or something else giving Haste from the command zone, or if the opponent has something on the battlefield that gives Haste to AI's creatures as well (e.g. Mass Hysteria). 2018-04-15 18:29:48 +03:00
Agetian
6a288419bc - Attempting to improve the AI timing for Indestructible pump abilities. 2018-04-15 16:29:11 +03:00
austinio7116
721ce4196b Support for listing an exhaustive list all formats a deck is valid in if required 2018-04-15 12:19:00 +01:00
austinio7116
3a427188dd Merge branch 'assorted-fixes' into 'master'
Fixed a bug that caused the deck not to reset properly after playing Momir/MoJhoSto.

Closes #510

See merge request core-developers/forge!407
2018-04-15 10:53:15 +00:00
Agetian
0ca1166f0c - No need to provide the name for autogen decks. 2018-04-15 13:27:53 +03:00
Agetian
ec2e04f401 - Fixed a bug that caused the deck not to reset properly after playing Momir/MoJhoSto. 2018-04-15 13:19:15 +03:00
Michael Kamensky
d220b49324 Merge branch 'master' into 'master'
- Updating CHANGES.txt.

See merge request core-developers/forge!406
2018-04-15 08:32:40 +00:00
Agetian
ec467a81db - Updating CHANGES.txt. 2018-04-15 11:31:41 +03:00
maustin
0265bcc038 Merge branch 'master' into historicformats 2018-04-15 09:10:12 +01:00
swordshine
85458cfe5a Merge branch 'ai-dealdamage-to-planeswalker' into 'master'
Simple direct damage to planeswalkers AI

See merge request core-developers/forge!381
2018-04-15 07:43:54 +00:00
Michael Kamensky
802d084070 Merge branch 'bugfixes' into 'master'
Fixed commander based deck generation error with adding random extra cards

See merge request core-developers/forge!405
2018-04-15 07:12:26 +00:00
austinio7116
dfa409da1b Fixed commander based deck generation error with adding random extra cards 2018-04-15 07:31:53 +01:00
austinio7116
c05a3c64ee Improved performance of checking deck format legality 2018-04-15 06:39:56 +01:00
austinio7116
17fc231db6 Fix to ensure invalid format files do not crash game on startup 2018-04-15 00:24:39 +01:00
schnautzr
9c6eb6e071 Formats up to but not including Mirage. 2018-04-14 16:08:04 -05:00
schnautzr
9034e0787d early 96 formats 2018-04-14 14:51:21 -05:00
austinio7116
a36b2ed13e Added some default date handling for gameformats 2018-04-14 20:48:57 +01:00
austinio7116
71647e5bc9 Implemented parser and comparator for using effectivedate in historic gameformats 2018-04-14 20:44:08 +01:00
maustin
7a8c82c226 Merge remote-tracking branch 'origin/historicformats' into historicformats 2018-04-14 20:39:16 +01:00
maustin
00ef5bc0e6 Merge branch 'dominariadraft' into historicformats
# Conflicts:
#	forge-game/src/main/java/forge/game/GameFormat.java
2018-04-14 20:31:56 +01:00
maustin
b5686b44c4 Dates for formats 2018-04-14 20:29:28 +01:00
schnautzr
431f329933 Sort formats 2018-04-14 13:46:23 -05:00
schnautzr
0e79da4944 Merge branch 'historicformats' of https://git.cardforge.org/core-developers/forge into historicformats 2018-04-14 13:31:25 -05:00
schnautzr
915f9f00f5 Iterating through the formats again and making some adjustments. Verifying lists. Finished with pre-Fourth-Edition formats. Some additional organization of files. 2018-04-14 13:30:05 -05:00
Michael Kamensky
a524ed4439 Merge branch 'dominariadraft' into 'master'
Added some missing raritites from the dominaria editions file - rat colony and voltaic servant

See merge request core-developers/forge!404
2018-04-14 18:15:44 +00:00
austinio7116
fbfc4c43f4 Added some missing raritites from the dominaria editions file - rat colony and voltaic servant 2018-04-14 19:13:39 +01:00
Rob Schnautz
9be7b35bd7 Add new directory 2018-04-14 17:51:48 +00:00
austinio7116
b20733d492 Changed logic for restricting legendary cards in format to exclude planeswalkers 2018-04-14 18:48:33 +01:00
austinio7116
f69a8af83f Added new RestrictedLegendary option to formats to enable blanket restriction on legendary cards 2018-04-14 18:41:37 +01:00
maustin
1145a3a513 Merge branch 'master' into historicformats 2018-04-14 17:42:09 +01:00
Michael Kamensky
1c4670e42f Merge branch 'dom-card-fixes' into 'master'
Helm of the Host ability triggers at beginning of combat

See merge request core-developers/forge!403
2018-04-14 15:57:03 +00:00
Luke Way
268ccc2cd5 Helm of the Host ability triggers at beginning of combat 2018-04-14 11:30:52 -04:00
Michael Kamensky
2e342b32f2 Merge branch 'muldrotha' into 'master'
MayPlay: fixed mayPlay with multiple static abilities

See merge request core-developers/forge!402
2018-04-14 14:57:48 +00:00
Hanmac
32737ed9a3 MayPlay: fixed mayPlay with multiple static abilities 2018-04-14 09:24:26 +02:00
austinio7116
a280772be0 Merge branch 'assorted-fixes' into 'master'
- Dominaria Planeswalker deck fix + minor update.

See merge request core-developers/forge!401
2018-04-14 05:31:17 +00:00
Agetian
f75ed58a97 - Fixed Teferi planeswalker deck.
- Somewhat more informative descriptions for Dominaria planeswalker decks.
2018-04-14 08:24:00 +03:00
Hans Mackowiak
f1084158e1 Merge branch 'KickedManaRestrict' into 'master'
AbilityManaPart: add valid check for the SA itself

Closes #445 and #456

See merge request core-developers/forge!384
2018-04-14 05:12:46 +00:00
Hanmac
aae587c618 SpellAbility: if it has MultikickerCost it is kicked too 2018-04-13 23:41:45 +02:00
Michael Kamensky
ac72402af4 Merge branch 'bugfixes' into 'master'
Removed promo card and planeswalker deck cards from booster drafts

See merge request core-developers/forge!400
2018-04-13 19:23:00 +00:00
austinio7116
f8d0281931 corrected typo 2018-04-13 20:08:59 +01:00
austinio7116
06e192f595 Removed promo card and planeswalker deck cards from booster drafts 2018-04-13 20:04:25 +01:00
Michael Kamensky
75eb551e22 Merge branch 'patch' into 'master'
DOM: A few fixes

See merge request core-developers/forge!399
2018-04-13 18:54:42 +00:00
Misha Colbourne
64796da026 Update spore_swarm.txt 2018-04-13 18:53:33 +00:00
Misha Colbourne
d00914a11c Update org.eclipse.core.resources.prefs 2018-04-13 18:50:41 +00:00
OgreBattlecruiser
325e404de1 DOM: A few fixes 2018-04-13 19:47:30 +01:00
Agetian
e28b683b89 - Correct some code style issues. 2018-04-13 21:43:31 +03:00
Agetian
82bd0325cf - Only process actual planeswalkers when choosing PW to damage. 2018-04-13 21:41:10 +03:00
Agetian
a88c8fba76 Merge remote-tracking branch 'origin/ai-dealdamage-to-planeswalker' into ai-dealdamage-to-planeswalker
# Conflicts:
#	forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
2018-04-13 21:31:00 +03:00
Michael Kamensky
f561f4d67b Merge branch 'bugfixes' into 'master'
Corrected call the cavalry's token image set code

See merge request core-developers/forge!398
2018-04-13 18:29:53 +00:00
Agetian
9a02c5394d - Only process planeswalkers in dealDamageChooseTgtPW. 2018-04-13 21:25:29 +03:00
Agetian
ec09738321 - Added a TODO entry. 2018-04-13 21:24:33 +03:00
Agetian
2258506383 - Do not consider "indirect Planeswalker burn" when deliberately evaluating a planeswalker vs. a player target (the clause must be removed and set as default later when the planeswalker redirection rule is fully removed). 2018-04-13 21:24:33 +03:00
Agetian
f2af85597e - Fixed a typo in the PLANESWALKERS card predicate preset. 2018-04-13 21:24:33 +03:00
Agetian
e4e08609d9 - Only process planeswalkers in dealDamageChooseTgtPW. 2018-04-13 21:24:33 +03:00
Agetian
98e16b2e28 - DealDamageAi for triggered direct damage to planeswalkers. 2018-04-13 21:24:33 +03:00
Agetian
5af6197c9d - Initial implementation: for the AI, consider dealing damage to planeswalkers directly when applicable. 2018-04-13 21:24:33 +03:00
maustin
73c9b94446 Merge branch 'master' into historicformats 2018-04-13 19:12:53 +01:00
maustin
0ad4af8927 Merge remote-tracking branch 'origin/bugfixes' into bugfixes 2018-04-13 19:06:15 +01:00
austinio7116
ad87aa3eca Corrected call the cavalry's token image set code 2018-04-13 19:05:59 +01:00
swordshine
3caf94b919 Merge branch 'dominariadraft' into 'master'
Added dominaria draft and rankings

See merge request core-developers/forge!396
2018-04-13 10:06:37 +00:00
austinio7116
3cc1246bf4 Added dominaria draft and rankings 2018-04-13 10:44:07 +01:00
swordshine
d89f732faa Merge branch 'blackblade' into 'master'
Blackblade reforged

Closes #230

See merge request core-developers/forge!394
2018-04-13 08:16:11 +00:00
maustin
2f6aa43d40 Merge branch 'master' into historicformats 2018-04-13 08:11:05 +01:00
austinio7116
15c9281988 Updated gameformat to only list one format of each historical subtype for each deck 2018-04-13 07:54:07 +01:00
Hanmac
2b8f650a6c add Blackblade reforged 2018-04-13 08:25:14 +02:00
swordshine
90aa0d8c61 Merge branch 'master' into 'master'
- Updated more cards

See merge request core-developers/forge!393
2018-04-13 04:30:28 +00:00
swordshine
ec5e8302d7 - Updated more cards 2018-04-13 12:29:56 +08:00
swordshine
ce1df9a7ec Merge branch 'master' into 'master'
- Updated some scripts

See merge request core-developers/forge!392
2018-04-13 03:45:26 +00:00
swordshine
421eb35e2c - Updated some scripts 2018-04-13 11:44:52 +08:00
swordshine
393b7c76bd Merge branch 'master' into 'master'
- Fixed two card's names

See merge request core-developers/forge!391
2018-04-13 03:08:00 +00:00
swordshine
efc4cbc07f - Fixed two card's names 2018-04-13 11:07:40 +08:00
swordshine
85f710dafa Merge branch 'master' into 'master'
Updated some scripts

See merge request core-developers/forge!390
2018-04-13 03:00:03 +00:00
swordshine
8380c23bca - Updated Fiery Intervention 2018-04-13 10:59:34 +08:00
swordshine
7ec77ee40a Merge branch 'master' into 'master'
Updated some scripts

See merge request core-developers/forge!389
2018-04-13 02:41:39 +00:00
swordshine
7c0c3ee25b - Updated Fungal Plots' card name 2018-04-13 10:40:56 +08:00
swordshine
b92a6ab04b - Updated Relic Runner 2018-04-13 10:39:11 +08:00
swordshine
1b19c85611 - Updated some scripts 2018-04-13 10:38:04 +08:00
swordshine
5e1848270f Merge branch 'master' into 'master'
Fixed Fiery Intervention's oracle text

See merge request core-developers/forge!388
2018-04-13 02:10:00 +00:00
swordshine
e1f262bd26 - Fixed Fiery Intervention's oracle text 2018-04-13 10:08:51 +08:00
swordshine
547d36793a Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!386
2018-04-13 02:02:43 +00:00
swordshine
31e711bd6a Merge branch 'planeswalker-decks' into 'master'
Planeswalker decks

See merge request core-developers/forge!387
2018-04-13 02:00:25 +00:00
swordshine
f2483766cf Merge branch 'dominaria' into 'master'
Dominaria editions file

See merge request core-developers/forge!330
2018-04-13 01:03:03 +00:00
Rob Schnautz
3ea41572bc Add new file 2018-04-13 00:35:45 +00:00
Rob Schnautz
1a29e845b1 Add new file 2018-04-13 00:35:14 +00:00
Rob Schnautz
e7cdc38284 Update Dominaria.txt 2018-04-12 23:19:36 +00:00
Misha Colbourne
69ad66ce49 Upload New File 2018-04-12 19:19:41 +00:00
Misha Colbourne
3602256bdf Upload New File 2018-04-12 19:16:17 +00:00
Misha Colbourne
99a3c88280 Upload New File 2018-04-12 19:13:48 +00:00
Misha Colbourne
fddfa257df Upload New File 2018-04-12 19:09:56 +00:00
Misha Colbourne
af7dc5a59a Upload New File 2018-04-12 19:04:43 +00:00
Misha Colbourne
31a50bb495 Upload New File 2018-04-12 19:02:22 +00:00
Misha Colbourne
f76d2adb7c Upload New File 2018-04-12 18:52:49 +00:00
Misha Colbourne
0017763514 Upload New File 2018-04-12 18:42:28 +00:00
Misha Colbourne
4cdf98dfaf Upload New File 2018-04-12 18:36:46 +00:00
Misha Colbourne
e3c833770a Upload New File 2018-04-12 18:24:15 +00:00
Misha Colbourne
1b62113fd3 Upload New File 2018-04-12 18:22:36 +00:00
Misha Colbourne
a9010d992e Upload New File 2018-04-12 18:18:40 +00:00
Misha Colbourne
2590c7a65f Upload New File 2018-04-12 18:03:51 +00:00
Misha Colbourne
ea163384c1 Upload New File 2018-04-12 18:01:46 +00:00
Misha Colbourne
93a0503455 Upload New File 2018-04-12 17:35:25 +00:00
Misha Colbourne
fb23bd0ac1 Update saproling_migration.txt 2018-04-12 17:35:06 +00:00
Misha Colbourne
c32486a213 Upload New File 2018-04-12 17:29:12 +00:00
Misha Colbourne
300d7f4662 Upload New File 2018-04-12 17:28:14 +00:00
Misha Colbourne
edabd66c98 Upload New File 2018-04-12 17:27:02 +00:00
Misha Colbourne
64ef53a3d6 Upload New File 2018-04-12 17:25:17 +00:00
Misha Colbourne
cbc006c50a Upload New File 2018-04-12 17:19:13 +00:00
Misha Colbourne
fa3ebfc9b5 Upload New File 2018-04-12 17:16:25 +00:00
Misha Colbourne
ba637664d5 Upload New File 2018-04-12 17:01:24 +00:00
Misha Colbourne
e1ec71b91b Update champion_of_the_flame.txt 2018-04-12 17:01:03 +00:00
Misha Colbourne
1498d27a64 Upload New File 2018-04-12 16:49:40 +00:00
Misha Colbourne
ddb8921c35 Update champion_of_the_flame.txt 2018-04-12 16:46:54 +00:00
Misha Colbourne
5ea35caa91 Upload New File 2018-04-12 16:45:48 +00:00
Misha Colbourne
8697472989 Update multani_yavimayas_avatar.txt 2018-04-12 16:36:23 +00:00
Misha Colbourne
d40e66f21c Upload New File 2018-04-12 16:17:26 +00:00
Misha Colbourne
709cc90bcb Upload New File 2018-04-12 16:15:24 +00:00
Misha Colbourne
a9c2a59441 Upload New File 2018-04-12 16:11:50 +00:00
Misha Colbourne
220d8e962e Upload New File 2018-04-12 16:02:42 +00:00
Misha Colbourne
00df39d2ea Upload New File 2018-04-12 15:43:06 +00:00
Misha Colbourne
10904c24a3 Upload New File 2018-04-12 15:39:54 +00:00
Misha Colbourne
c06bd35bd5 Upload New File 2018-04-12 15:37:08 +00:00
Misha Colbourne
6f4fd0ae2a Upload New File 2018-04-12 15:32:17 +00:00
Misha Colbourne
05569ca1ee Upload New File 2018-04-12 15:26:18 +00:00
Misha Colbourne
35655673fa DOM: Added some cards 2018-04-12 15:20:00 +00:00
Misha Colbourne
23c17b1937 DOM: Added some cards 2018-04-12 15:17:05 +00:00
Misha Colbourne
39369d6eb0 DOM: Added some cards 2018-04-12 15:13:32 +00:00
Misha Colbourne
5c3a974c90 DOM: Added some cards 2018-04-12 15:09:57 +00:00
Misha Colbourne
33613369cd DOM: Added some cards 2018-04-12 15:07:37 +00:00
Misha Colbourne
866cdd3704 DOM: Added some cards 2018-04-12 15:03:21 +00:00
Misha Colbourne
468b9d655a DOM: Added some cards 2018-04-12 15:02:17 +00:00
Misha Colbourne
28c78a1339 DOM: Added some cards 2018-04-12 14:54:31 +00:00
austinio7116
5df48873eb Update of game formats from latest cleanup - plus moved to individual folders and extended subtypes 2018-04-12 09:05:27 +01:00
Michael Kamensky
b80e5c712a Merge branch 'assorted-fixes' into 'master'
- AI: Don't hold land drops till main 2 if playing with the Momir avatar

See merge request core-developers/forge!385
2018-04-12 05:42:41 +00:00
Agetian
b54403163d - Don't hold land drops till main 2 if playing with the Momir avatar (Momir Basic, MoJhoSto, etc.), otherwise it messes with the AI strategy for activating the avatar. 2018-04-12 08:41:56 +03:00
Hanmac
f7bed9089c AbilityManaPart: add valid check for the SA itself
split later into extra check for SA and Host
2018-04-12 07:16:31 +02:00
Agetian
d325ce7a56 Merge remote-tracking branch 'origin/ai-dealdamage-to-planeswalker' into ai-dealdamage-to-planeswalker 2018-04-12 07:10:00 +03:00
Agetian
a3dc449cc9 - Added a TODO entry. 2018-04-12 07:09:40 +03:00
Agetian
c20093280c - Do not consider "indirect Planeswalker burn" when deliberately evaluating a planeswalker vs. a player target (the clause must be removed and set as default later when the planeswalker redirection rule is fully removed). 2018-04-12 07:09:40 +03:00
Agetian
115720cc53 - Fixed a typo in the PLANESWALKERS card predicate preset. 2018-04-12 07:09:40 +03:00
Agetian
cd1c0cc526 - Only process planeswalkers in dealDamageChooseTgtPW. 2018-04-12 07:09:40 +03:00
Agetian
8c53689e17 - DealDamageAi for triggered direct damage to planeswalkers. 2018-04-12 07:09:40 +03:00
Agetian
d3fe888238 - Initial implementation: for the AI, consider dealing damage to planeswalkers directly when applicable. 2018-04-12 07:09:40 +03:00
swordshine
333ba13869 Merge branch 'staticDamageToReplace' into 'master'
Replacement Effect instead of StaticDamage

See merge request core-developers/forge!332
2018-04-12 00:43:22 +00:00
swordshine
d77fa6a390 Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!383
2018-04-12 00:42:35 +00:00
swordshine
19b86606ce Merge branch 'rix-possibilitystorm' into 'master'
Rivals of Ixalan Possibility Storm puzzle season.

See merge request core-developers/forge!372
2018-04-12 00:40:40 +00:00
swordshine
6ae9f5468a Merge branch 'netdecks' into 'master'
Added some of Gos's mtggoldfish decklists to netdecks

See merge request core-developers/forge!382
2018-04-12 00:40:10 +00:00
Misha Colbourne
df2f48b860 DOM: Added some cards 2018-04-12 00:33:41 +00:00
Misha Colbourne
daae5ecec6 DOM: Added some cards 2018-04-11 23:27:43 +00:00
Misha Colbourne
ab0d6ecc1f Upload New File 2018-04-11 23:25:02 +00:00
Rob Schnautz
f4ec8352d0 Update Dominaria.txt 2018-04-11 23:07:05 +00:00
austinio7116
fc0777b9ff Added some of Gos's mtggoldfish decklists to netdecks 2018-04-11 22:54:40 +01:00
Michael Kamensky
55e492ee5d Merge branch 'assorted-fixes' into 'master'
- Fixed Chandra, Bold Pyromancer (unused subability reference).

See merge request core-developers/forge!380
2018-04-11 19:09:35 +00:00
Agetian
5530adca42 - Fixed Chandra, Bold Pyromancer (unused subability reference). 2018-04-11 22:08:58 +03:00
Michael Kamensky
7c4e0329bb Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!379
2018-04-11 18:51:35 +00:00
Agetian
6ff0dd5327 - Added puzzle PS_RIX10 (RIX Sunset Puzzle). 2018-04-11 21:03:17 +03:00
Agetian
9a5b2594f8 - Added PS_RIX9 puzzle. 2018-04-11 20:42:25 +03:00
Agetian
6f57dab6ba - Added PS_RIX8 puzzle. 2018-04-11 20:15:43 +03:00
Agetian
5154bbc070 - Added PS_RIX7 puzzle. 2018-04-11 19:47:29 +03:00
Misha Colbourne
c20029003c DOM: Added some cards 2018-04-11 15:07:53 +00:00
Misha Colbourne
6a9e95b70a Update thran_temporal_gateway.txt 2018-04-11 15:00:18 +00:00
Misha Colbourne
dfce092dbb DOM: Added some cards 2018-04-11 14:59:57 +00:00
Misha Colbourne
8b59040a9a DOM: Added some cards 2018-04-11 14:49:37 +00:00
Misha Colbourne
bb39f66a32 DOM: Added some cards 2018-04-11 14:36:18 +00:00
Michael Kamensky
019bc27cf2 Merge branch 'master' into 'master'
Fixed two bugs

Closes #9

See merge request core-developers/forge!378
2018-04-11 12:10:20 +00:00
swordshine
ab6de1752f - Fixed issue #9 (Angrath, Minotaur Pirate vs. indestructible creatures
)
2018-04-11 17:48:47 +08:00
swordshine
5a9dc2e09a - fixed issure 431 2018-04-11 16:44:33 +08:00
Agetian
de45adf797 - Added PS_RIX6 puzzle. 2018-04-11 09:33:25 +03:00
Agetian
d308fba3eb - Added PS_RIX5 puzzle. 2018-04-11 09:16:09 +03:00
Agetian
3c5e4dd944 - Added a TODO entry. 2018-04-11 08:58:27 +03:00
Agetian
43a7e895a4 - Do not consider "indirect Planeswalker burn" when deliberately evaluating a planeswalker vs. a player target (the clause must be removed and set as default later when the planeswalker redirection rule is fully removed). 2018-04-11 08:56:14 +03:00
Agetian
086ea97365 - Flavor: Forests in PS_RIX3 are now from Ixalan. 2018-04-11 08:36:19 +03:00
Agetian
1c9b3e5d22 - Added PS_RIX4 puzzle. 2018-04-11 08:33:23 +03:00
Agetian
2db32d289f - Fixed a typo in the PLANESWALKERS card predicate preset. 2018-04-11 08:05:47 +03:00
Agetian
4d33823f28 - Only process planeswalkers in dealDamageChooseTgtPW. 2018-04-11 07:48:48 +03:00
Agetian
73b075d0e7 - DealDamageAi for triggered direct damage to planeswalkers. 2018-04-11 07:31:00 +03:00
Agetian
6f93fb29b4 - Initial implementation: for the AI, consider dealing damage to planeswalkers directly when applicable. 2018-04-11 07:16:58 +03:00
Agetian
0bf80df486 - Clarification for PS_RIX3. 2018-04-10 22:59:03 +03:00
Agetian
b84aecc1c6 - Added PS_RIX3 puzzle. 2018-04-10 22:57:57 +03:00
Agetian
2ca008861d - Fixed the transformed state of Azor's Gateway in PS_RIX2. 2018-04-10 22:18:07 +03:00
Agetian
4362291fb7 Merge remote-tracking branch 'origin/rix-possibilitystorm' into rix-possibilitystorm 2018-04-10 22:14:31 +03:00
Agetian
563f818523 - Updating CHANGES.txt for RIX Possibility Storm. 2018-04-10 22:13:20 +03:00
Agetian
28b4355d7b - Added PS_RIX2 puzzle. 2018-04-10 22:11:29 +03:00
Agetian
faca4089b7 - Added PS_RIX1 puzzle.
- Fixed Puzzle Mode init for setting the player's hand size which sometimes failed to work.
2018-04-10 22:11:29 +03:00
Michael Kamensky
e7c8fe966f Merge branch 'master' into 'master'
- Updating CHANGES.txt.

See merge request core-developers/forge!377
2018-04-10 19:08:17 +00:00
Agetian
9e2447f601 - Updating CHANGES.txt. 2018-04-10 22:06:20 +03:00
Agetian
3b8059281b Merge remote-tracking branch 'origin/rix-possibilitystorm' into rix-possibilitystorm 2018-04-10 21:53:29 +03:00
Agetian
4103a007db - Added PS_RIX2 puzzle. 2018-04-10 21:45:30 +03:00
maustin
e59446c628 Merge branch 'master' into historicformats 2018-04-10 19:27:34 +01:00
Michael Kamensky
5e548465dc Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!375
2018-04-10 18:18:53 +00:00
Agetian
52cbacd306 - Added PS_RIX1 puzzle.
- Fixed Puzzle Mode init for setting the player's hand size which sometimes failed to work.
2018-04-10 20:54:44 +03:00
Michael Kamensky
666459c011 Merge branch 'patch-1else' into 'master'
Fix CopyPermanentEffect

See merge request core-developers/forge!376
2018-04-10 17:50:18 +00:00
Hans Mackowiak
f6dc955d4b Update CopyPermanentEffect 2018-04-10 17:48:30 +00:00
Misha Colbourne
a71f4dc0b2 Update orcish_vandal.txt 2018-04-10 16:32:13 +00:00
Misha Colbourne
939b25efd8 DOM: Added some cards 2018-04-10 16:22:50 +00:00
Misha Colbourne
729f35b6c1 DOM: Added some cards 2018-04-10 16:20:32 +00:00
Misha Colbourne
97995f9b8c DOM: Added some cards 2018-04-10 16:16:07 +00:00
Misha Colbourne
2997deac66 DOM: Added some cards 2018-04-10 16:13:29 +00:00
Misha Colbourne
a77e12b078 DOM: Added some cards 2018-04-10 16:06:23 +00:00
maustin
f84ec51206 Merge branch 'master' into historicformats 2018-04-10 14:34:10 +01:00
austinio7116
421b70b4f6 Merge branch 'dom-card-fixes' into 'master'
DOM Card Fixes

See merge request core-developers/forge!374
2018-04-10 13:31:19 +00:00
swordshine
1911d6915c Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!373
2018-04-10 13:10:45 +00:00
maustin
b4526df691 Merge branch 'master' into historicformats 2018-04-10 14:07:06 +01:00
Luke Way
6e0ba80d12 Tokens created with Karn's third ability only buff themselves 2018-04-10 09:05:54 -04:00
Luke Way
48fdac42f4 Tempest Djinn has Flying 2018-04-10 09:04:54 -04:00
Luke Way
cb5dca5c69 Zahid is Legendary 2018-04-10 09:04:16 -04:00
Misha Colbourne
36c699970b DOM: Added some cards 2018-04-10 12:23:52 +00:00
Misha Colbourne
7ebb70247c DOM: Added some cards 2018-04-10 12:21:38 +00:00
Misha Colbourne
4a65c70668 DOM: Added some cards 2018-04-10 12:20:04 +00:00
austinio7116
4f6cddebd3 Readded corrected type 1/2 renamed formats 2018-04-10 10:06:41 +01:00
Michael Kamensky
b7a0500f25 Merge branch 'cleanup-multiplayer-android' into 'master'
Cleanup multiplayer Android code, fix player heights in two player mode in Landscape orientation

See merge request core-developers/forge!371
2018-04-10 08:58:33 +00:00
Agetian
809b047523 - Added PS_RIX1 puzzle.
- Fixed Puzzle Mode init for setting the player's hand size which sometimes failed to work.
2018-04-10 11:56:07 +03:00
maustin
ae2c56abce Updated GameFormat reader to support nested folders plus user defined custom formats from their local user directory. Moved Digital formats to a subfolder to demonstrate 2018-04-10 09:49:02 +01:00
austinio7116
68ad165fb1 Updated tooltip on filter to show additional cards in format
Updated restricted and banned lists from new historic formats to use semicolons instead of commas as required
2018-04-10 09:26:40 +01:00
Agetian
afe040d0b6 - Minor cleanup. 2018-04-10 10:58:51 +03:00
Agetian
9a43c1b3ae - Ensure that players have equal player panel heights in a two-player game in Landscape mode 2018-04-10 10:56:33 +03:00
Agetian
7e58e204ed - Code style cleanup/unification in multiplayer Forge for Android. 2018-04-10 10:49:24 +03:00
maustin
e51d39dcc3 Removed duplicate renamed formats
Corrected extended additional cards list to separate with ; not , - I've realized all the other historic formats have this issue in their banned/restricted list and will need updating
2018-04-10 08:25:08 +01:00
maustin
93047c5fda Merge remote-tracking branch 'origin/historicformats' into historicformats
# Conflicts:
#	forge-game/src/main/java/forge/game/GameFormat.java
#	forge-gui-desktop/src/main/java/forge/screens/home/quest/DialogChooseFormats.java
#	forge-gui-mobile/src/forge/itemmanager/filters/FormatFilter.java
#	forge-gui/res/formats/Amonkhet Block.txt
#	forge-gui/res/formats/Antiquities.txt
#	forge-gui/res/formats/Arabian Nights.txt
#	forge-gui/res/formats/Aug 2 1994.txt
#	forge-gui/res/formats/Battle for Zendikar Block.txt
#	forge-gui/res/formats/Classic Restricted, Apr 1999.txt
#	forge-gui/res/formats/Classic Restricted, Classic Sixth Edition.txt
#	forge-gui/res/formats/Classic Restricted, Exodus.txt
#	forge-gui/res/formats/Classic Restricted, Fifth Edition.txt
#	forge-gui/res/formats/Classic Restricted, Jan 1997.txt
#	forge-gui/res/formats/Classic Restricted, Jan 1999.txt
#	forge-gui/res/formats/Classic Restricted, Mirage.txt
#	forge-gui/res/formats/Classic Restricted, Oct 1996.txt
#	forge-gui/res/formats/Classic Restricted, Stronghold.txt
#	forge-gui/res/formats/Classic Restricted, Tempest.txt
#	forge-gui/res/formats/Classic Restricted, Urza's Destiny.txt
#	forge-gui/res/formats/Classic Restricted, Urza's Legacy.txt
#	forge-gui/res/formats/Classic Restricted, Urza's Saga.txt
#	forge-gui/res/formats/Classic Restricted, Visions.txt
#	forge-gui/res/formats/Classic Restricted, Weatherlight.txt
#	forge-gui/res/formats/Classic, Apr 1999.txt
#	forge-gui/res/formats/Classic, Classic Sixth Edition.txt
#	forge-gui/res/formats/Classic, Exodus.txt
#	forge-gui/res/formats/Classic, Fifth Edition.txt
#	forge-gui/res/formats/Classic, Mirage.txt
#	forge-gui/res/formats/Classic, Oct 1996.txt
#	forge-gui/res/formats/Classic, Stronghold.txt
#	forge-gui/res/formats/Classic, Tempest.txt
#	forge-gui/res/formats/Classic, Urza's Destiny.txt
#	forge-gui/res/formats/Classic, Urza's Legacy.txt
#	forge-gui/res/formats/Classic, Urza's Saga.txt
#	forge-gui/res/formats/Classic, Visions.txt
#	forge-gui/res/formats/Classic, Weatherlight.txt
#	forge-gui/res/formats/Conspiracy.txt
#	forge-gui/res/formats/Duels.txt
#	forge-gui/res/formats/Extended, Alara Reborn.txt
#	forge-gui/res/formats/Extended, Apr 1999.txt
#	forge-gui/res/formats/Extended, Apr 2000.txt
#	forge-gui/res/formats/Extended, Apr 2001.txt
#	forge-gui/res/formats/Extended, Aug 1999.txt
#	forge-gui/res/formats/Extended, Avacyn Restored.txt
#	forge-gui/res/formats/Extended, Betrayers of Kamigawa.txt
#	forge-gui/res/formats/Extended, Champions of Kamigawa.txt
#	forge-gui/res/formats/Extended, Classic Sixth Edition.txt
#	forge-gui/res/formats/Extended, Coldsnap.txt
#	forge-gui/res/formats/Extended, Conflux.txt
#	forge-gui/res/formats/Extended, Dark Ascension.txt
#	forge-gui/res/formats/Extended, Darksteel.txt
#	forge-gui/res/formats/Extended, Dissension.txt
#	forge-gui/res/formats/Extended, Eighth Edition.txt
#	forge-gui/res/formats/Extended, Eventide.txt
#	forge-gui/res/formats/Extended, Exodus.txt
#	forge-gui/res/formats/Extended, Fifth Dawn.txt
#	forge-gui/res/formats/Extended, Future Sight.txt
#	forge-gui/res/formats/Extended, Guildpact.txt
#	forge-gui/res/formats/Extended, Innistrad.txt
#	forge-gui/res/formats/Extended, Invasion.txt
#	forge-gui/res/formats/Extended, Jan 1999.txt
#	forge-gui/res/formats/Extended, Jan 2004.txt
#	forge-gui/res/formats/Extended, Judgment.txt
#	forge-gui/res/formats/Extended, July 2010.txt
#	forge-gui/res/formats/Extended, Legions.txt
#	forge-gui/res/formats/Extended, Lorwyn.txt
#	forge-gui/res/formats/Extended, Magic 2010.txt
#	forge-gui/res/formats/Extended, Magic 2011.txt
#	forge-gui/res/formats/Extended, Magic 2012.txt
#	forge-gui/res/formats/Extended, Magic 2013.txt
#	forge-gui/res/formats/Extended, Mercadian Masques.txt
#	forge-gui/res/formats/Extended, Mirrodin Besieged.txt
#	forge-gui/res/formats/Extended, Mirrodin.txt
#	forge-gui/res/formats/Extended, Morningtide.txt
#	forge-gui/res/formats/Extended, Nemesis.txt
#	forge-gui/res/formats/Extended, New Phyrexia.txt
#	forge-gui/res/formats/Extended, Ninth Edition.txt
#	forge-gui/res/formats/Extended, Oct 1999.txt
#	forge-gui/res/formats/Extended, Oct 2003.txt
#	forge-gui/res/formats/Extended, October 2011.txt
#	forge-gui/res/formats/Extended, Odyssey.txt
#	forge-gui/res/formats/Extended, Onslaught.txt
#	forge-gui/res/formats/Extended, Planar Chaos.txt
#	forge-gui/res/formats/Extended, Planeshift.txt
#	forge-gui/res/formats/Extended, Prophecy.txt
#	forge-gui/res/formats/Extended, Ravnica_ City of Guilds.txt
#	forge-gui/res/formats/Extended, Rise of the Eldrazi.txt
#	forge-gui/res/formats/Extended, Saviors of Kamigawa.txt
#	forge-gui/res/formats/Extended, Scars of Mirrodin.txt
#	forge-gui/res/formats/Extended, Scourge.txt
#	forge-gui/res/formats/Extended, Sep 2004.txt
#	forge-gui/res/formats/Extended, Sep 2005.txt
#	forge-gui/res/formats/Extended, Sep 2008.txt
#	forge-gui/res/formats/Extended, Seventh Edition.txt
#	forge-gui/res/formats/Extended, Shadowmoor.txt
#	forge-gui/res/formats/Extended, Shards of Alara.txt
#	forge-gui/res/formats/Extended, Stronghold.txt
#	forge-gui/res/formats/Extended, Tempest.txt
#	forge-gui/res/formats/Extended, Tenth Edition.txt
#	forge-gui/res/formats/Extended, Time Spiral.txt
#	forge-gui/res/formats/Extended, Torment.txt
#	forge-gui/res/formats/Extended, Urza's Destiny.txt
#	forge-gui/res/formats/Extended, Urza's Legacy.txt
#	forge-gui/res/formats/Extended, Urza's Saga.txt
#	forge-gui/res/formats/Extended, Weatherlight.txt
#	forge-gui/res/formats/Extended, Worldwake.txt
#	forge-gui/res/formats/Extended, Zendikar.txt
#	forge-gui/res/formats/Fallen Empires.txt
#	forge-gui/res/formats/Feb 23 1994.txt
#	forge-gui/res/formats/Ice Age Block Constructed, Coldsnap.txt
#	forge-gui/res/formats/Ice Age Block.txt
#	forge-gui/res/formats/Ice Age-Only, Oct 1996.txt
#	forge-gui/res/formats/Innistrad Block Constructed, April 2012.txt
#	forge-gui/res/formats/Innistrad Block Constructed, Avacyn Restored.txt
#	forge-gui/res/formats/Innistrad Block Constructed, Dark Ascension.txt
#	forge-gui/res/formats/Innistrad Block Constructed, Innistrad.txt
#	forge-gui/res/formats/Innistrad-Avacyn Restored Block.txt
#	forge-gui/res/formats/Invasion Block Constructed, Apocalypse.txt
#	forge-gui/res/formats/Invasion Block Constructed, Invasion.txt
#	forge-gui/res/formats/Invasion Block Constructed, Planeshift.txt
#	forge-gui/res/formats/Invasion Block.txt
#	forge-gui/res/formats/Ixalan Block.txt
#	forge-gui/res/formats/Jan 1999 Classic.txt
#	forge-gui/res/formats/Jan 25 1994.txt
#	forge-gui/res/formats/Jun 13 1994.txt
#	forge-gui/res/formats/Kaladesh Block.txt
#	forge-gui/res/formats/Kamigawa Block Constructed, Betrayers of Kamigawa.txt
#	forge-gui/res/formats/Kamigawa Block Constructed, Champions of Kamigawa.txt
#	forge-gui/res/formats/Kamigawa Block Constructed, Saviors of Kamigawa.txt
#	forge-gui/res/formats/Kamigawa Block.txt
#	forge-gui/res/formats/Khans of Tarkir Block.txt
#	forge-gui/res/formats/Legacy, Alara Reborn.txt
#	forge-gui/res/formats/Legacy, Avacyn Restored.txt
#	forge-gui/res/formats/Legacy, Betrayers of Kamigawa.txt
#	forge-gui/res/formats/Legacy, Champions of Kamigawa.txt
#	forge-gui/res/formats/Legacy, Coldsnap.txt
#	forge-gui/res/formats/Legacy, Conflux.txt
#	forge-gui/res/formats/Legacy, Dark Ascension.txt
#	forge-gui/res/formats/Legacy, Dissension.txt
#	forge-gui/res/formats/Legacy, Eventide.txt
#	forge-gui/res/formats/Legacy, Future Sight.txt
#	forge-gui/res/formats/Legacy, Guildpact.txt
#	forge-gui/res/formats/Legacy, Innistrad.txt
#	forge-gui/res/formats/Legacy, January 2011.txt
#	forge-gui/res/formats/Legacy, July 2010.txt
#	forge-gui/res/formats/Legacy, July 2012.txt
#	forge-gui/res/formats/Legacy, Jun 2007.txt
#	forge-gui/res/formats/Legacy, Lorwyn.txt
#	forge-gui/res/formats/Legacy, Magic 2010.txt
#	forge-gui/res/formats/Legacy, Magic 2011.txt
#	forge-gui/res/formats/Legacy, Magic 2012.txt
#	forge-gui/res/formats/Legacy, Magic 2013.txt
#	forge-gui/res/formats/Legacy, Mirrodin Besieged.txt
#	forge-gui/res/formats/Legacy, Morningtide.txt
#	forge-gui/res/formats/Legacy, New Phyrexia.txt
#	forge-gui/res/formats/Legacy, Ninth Edition.txt
#	forge-gui/res/formats/Legacy, October 2009.txt
#	forge-gui/res/formats/Legacy, Planar Chaos.txt
#	forge-gui/res/formats/Legacy, Ravnica_ City of Guilds.txt
#	forge-gui/res/formats/Legacy, Rise of the Eldrazi.txt
#	forge-gui/res/formats/Legacy, Saviors of Kamigawa.txt
#	forge-gui/res/formats/Legacy, Scars of Mirrodin.txt
#	forge-gui/res/formats/Legacy, Sep 2007.txt
#	forge-gui/res/formats/Legacy, Sep 2008.txt
#	forge-gui/res/formats/Legacy, Shadowmoor.txt
#	forge-gui/res/formats/Legacy, Shards of Alara.txt
#	forge-gui/res/formats/Legacy, Tenth Edition.txt
#	forge-gui/res/formats/Legacy, Time Spiral.txt
#	forge-gui/res/formats/Legacy, Worldwake.txt
#	forge-gui/res/formats/Legacy, Zendikar.txt
#	forge-gui/res/formats/Legends.txt
#	forge-gui/res/formats/Limited Edition Alpha.txt
#	forge-gui/res/formats/Limited Edition Beta.txt
#	forge-gui/res/formats/Lorwyn Block Constructed, Lorwyn.txt
#	forge-gui/res/formats/Lorwyn Block Constructed, Morningtide.txt
#	forge-gui/res/formats/Lorwyn-Shadowmoor Block Constructed, Eventide.txt
#	forge-gui/res/formats/Lorwyn-Shadowmoor Block Constructed, Shadowmoor.txt
#	forge-gui/res/formats/Lorwyn-Shadowmoor Block.txt
#	forge-gui/res/formats/MTGO.txt
#	forge-gui/res/formats/Mar 23 1994.txt
#	forge-gui/res/formats/Masques Block Constructed, Mercadian Masques.txt
#	forge-gui/res/formats/Masques Block Constructed, Nemesis.txt
#	forge-gui/res/formats/Masques Block Constructed, Prophecy.txt
#	forge-gui/res/formats/Masques Block.txt
#	forge-gui/res/formats/May 1994.txt
#	forge-gui/res/formats/MicroProse.txt
#	forge-gui/res/formats/Mirage Block.txt
#	forge-gui/res/formats/Mirrodin Block Constructed, Darksteel.txt
#	forge-gui/res/formats/Mirrodin Block Constructed, Fifth Dawn.txt
#	forge-gui/res/formats/Mirrodin Block Constructed, Jun 2004.txt
#	forge-gui/res/formats/Mirrodin Block Constructed, Mar 2006.txt
#	forge-gui/res/formats/Mirrodin Block Constructed, Mirrodin.txt
#	forge-gui/res/formats/Mirrodin Block.txt
#	forge-gui/res/formats/Modern, August 2011.txt
#	forge-gui/res/formats/Modern, Avacyn Restored.txt
#	forge-gui/res/formats/Modern, Dark Ascension.txt
#	forge-gui/res/formats/Modern, Innistrad.txt
#	forge-gui/res/formats/Modern, January 2012.txt
#	forge-gui/res/formats/Modern, Magic 2013.txt
#	forge-gui/res/formats/Modern, October 2011.txt
#	forge-gui/res/formats/Oct 10 1994.txt
#	forge-gui/res/formats/Odyssey Block Constructed, Judgment.txt
#	forge-gui/res/formats/Odyssey Block Constructed, Odyssey.txt
#	forge-gui/res/formats/Odyssey Block Constructed, Torment.txt
#	forge-gui/res/formats/Odyssey Block.txt
#	forge-gui/res/formats/Onslaught Block Constructed, Legions.txt
#	forge-gui/res/formats/Onslaught Block Constructed, Onslaught.txt
#	forge-gui/res/formats/Onslaught Block Constructed, Scourge.txt
#	forge-gui/res/formats/Onslaught Block.txt
#	forge-gui/res/formats/Ravnica Block Constructed, Dissension.txt
#	forge-gui/res/formats/Ravnica Block Constructed, Guildpact.txt
#	forge-gui/res/formats/Ravnica Block Constructed, Ravnica_ City of Guilds.txt
#	forge-gui/res/formats/Ravnica Block.txt
#	forge-gui/res/formats/Return to Ravnica Block.txt
#	forge-gui/res/formats/Revised Edition.txt
#	forge-gui/res/formats/Scars of Mirrodin Block Constructed, Mirrodin Besieged.txt
#	forge-gui/res/formats/Scars of Mirrodin Block Constructed, New Phyrexia.txt
#	forge-gui/res/formats/Scars of Mirrodin Block Constructed, Scars of Mirrodin.txt
#	forge-gui/res/formats/Scars of Mirrodin Block.txt
#	forge-gui/res/formats/Shadows over Innistrad Block.txt
#	forge-gui/res/formats/Shards of Alara Block Constructed, Alara Reborn.txt
#	forge-gui/res/formats/Shards of Alara Block Constructed, Conflux.txt
#	forge-gui/res/formats/Shards of Alara Block Constructed, Shards of Alara.txt
#	forge-gui/res/formats/Shards of Alara Block.txt
#	forge-gui/res/formats/Standard, Alara Reborn.txt
#	forge-gui/res/formats/Standard, Apocalypse.txt
#	forge-gui/res/formats/Standard, Apr 1999.txt
#	forge-gui/res/formats/Standard, Avacyn Restored.txt
#	forge-gui/res/formats/Standard, Betrayers of Kamigawa.txt
#	forge-gui/res/formats/Standard, Champions of Kamigawa.txt
#	forge-gui/res/formats/Standard, Classic Sixth Edition.txt
#	forge-gui/res/formats/Standard, Coldsnap.txt
#	forge-gui/res/formats/Standard, Conflux.txt
#	forge-gui/res/formats/Standard, Dark Ascension.txt
#	forge-gui/res/formats/Standard, Darksteel.txt
#	forge-gui/res/formats/Standard, Dissension.txt
#	forge-gui/res/formats/Standard, Eighth Edition.txt
#	forge-gui/res/formats/Standard, Eventide.txt
#	forge-gui/res/formats/Standard, Exodus.txt
#	forge-gui/res/formats/Standard, Fifth Dawn.txt
#	forge-gui/res/formats/Standard, Fifth Edition.txt
#	forge-gui/res/formats/Standard, Future Sight.txt
#	forge-gui/res/formats/Standard, Guildpact.txt
#	forge-gui/res/formats/Standard, Innistrad.txt
#	forge-gui/res/formats/Standard, Invasion.txt
#	forge-gui/res/formats/Standard, Jan 1997.txt
#	forge-gui/res/formats/Standard, Jan 1999.txt
#	forge-gui/res/formats/Standard, Judgment.txt
#	forge-gui/res/formats/Standard, July 2011.txt
#	forge-gui/res/formats/Standard, Jun 2004.txt
#	forge-gui/res/formats/Standard, Legions.txt
#	forge-gui/res/formats/Standard, Lorwyn.txt
#	forge-gui/res/formats/Standard, Magic 2010.txt
#	forge-gui/res/formats/Standard, Magic 2011.txt
#	forge-gui/res/formats/Standard, Magic 2012.txt
#	forge-gui/res/formats/Standard, Magic 2013.txt
#	forge-gui/res/formats/Standard, Mar 2005.txt
#	forge-gui/res/formats/Standard, Mercadian Masques.txt
#	forge-gui/res/formats/Standard, Mirage.txt
#	forge-gui/res/formats/Standard, Mirrodin Besieged.txt
#	forge-gui/res/formats/Standard, Mirrodin.txt
#	forge-gui/res/formats/Standard, Morningtide.txt
#	forge-gui/res/formats/Standard, Nemesis.txt
#	forge-gui/res/formats/Standard, New Phyrexia.txt
#	forge-gui/res/formats/Standard, Ninth Edition.txt
#	forge-gui/res/formats/Standard, Oct 1996.txt
#	forge-gui/res/formats/Standard, Odyssey.txt
#	forge-gui/res/formats/Standard, Onslaught.txt
#	forge-gui/res/formats/Standard, Planar Chaos.txt
#	forge-gui/res/formats/Standard, Planeshift.txt
#	forge-gui/res/formats/Standard, Prophecy.txt
#	forge-gui/res/formats/Standard, Ravnica_City of Guilds.txt
#	forge-gui/res/formats/Standard, Rise of the Eldrazi.txt
#	forge-gui/res/formats/Standard, Saviors of Kamigawa.txt
#	forge-gui/res/formats/Standard, Scars of Mirrodin.txt
#	forge-gui/res/formats/Standard, Scourge.txt
#	forge-gui/res/formats/Standard, Seventh Edition.txt
#	forge-gui/res/formats/Standard, Shadowmoor.txt
#	forge-gui/res/formats/Standard, Shards of Alara.txt
#	forge-gui/res/formats/Standard, Stronghold.txt
#	forge-gui/res/formats/Standard, Tempest.txt
#	forge-gui/res/formats/Standard, Tenth Edition.txt
#	forge-gui/res/formats/Standard, Time Spiral.txt
#	forge-gui/res/formats/Standard, Torment.txt
#	forge-gui/res/formats/Standard, Urza's Destiny.txt
#	forge-gui/res/formats/Standard, Urza's Legacy.txt
#	forge-gui/res/formats/Standard, Urza's Saga.txt
#	forge-gui/res/formats/Standard, Visions.txt
#	forge-gui/res/formats/Standard, Weatherlight.txt
#	forge-gui/res/formats/Standard, Worldwake.txt
#	forge-gui/res/formats/Standard, Zendikar.txt
#	forge-gui/res/formats/Tempest Block, Tempest.txt
#	forge-gui/res/formats/Tempest Block.txt
#	forge-gui/res/formats/The Dark.txt
#	forge-gui/res/formats/Theros Block.txt
#	forge-gui/res/formats/Time Spiral Block Constructed, Future Sight.txt
#	forge-gui/res/formats/Time Spiral Block Constructed, Planar Chaos.txt
#	forge-gui/res/formats/Time Spiral Block Constructed, Time Spiral.txt
#	forge-gui/res/formats/Time Spiral Block.txt
#	forge-gui/res/formats/Type 1, Apocalypse.txt
#	forge-gui/res/formats/Type 1, Apr 2003.txt
#	forge-gui/res/formats/Type 1, Chronicles.txt
#	forge-gui/res/formats/Type 1, Darksteel.txt
#	forge-gui/res/formats/Type 1, Eighth Edition.txt
#	forge-gui/res/formats/Type 1, Fifth Dawn.txt
#	forge-gui/res/formats/Type 1, Fourth Edition.txt
#	forge-gui/res/formats/Type 1, Ice Age.txt
#	forge-gui/res/formats/Type 1, Invasion.txt
#	forge-gui/res/formats/Type 1, Jan 2002.txt
#	forge-gui/res/formats/Type 1, Jan 2004.txt
#	forge-gui/res/formats/Type 1, Judgment.txt
#	forge-gui/res/formats/Type 1, Jul 2003.txt
#	forge-gui/res/formats/Type 1, Legions.txt
#	forge-gui/res/formats/Type 1, Mercadian Masques.txt
#	forge-gui/res/formats/Type 1, Mirrodin.txt
#	forge-gui/res/formats/Type 1, Nemesis.txt
#	forge-gui/res/formats/Type 1, Oct 1999.txt
#	forge-gui/res/formats/Type 1, Oct 2000.txt
#	forge-gui/res/formats/Type 1, Odyssey.txt
#	forge-gui/res/formats/Type 1, Onslaught.txt
#	forge-gui/res/formats/Type 1, Planeshift.txt
#	forge-gui/res/formats/Type 1, Prophecy.txt
#	forge-gui/res/formats/Type 1, Scourge.txt
#	forge-gui/res/formats/Type 1, Seventh Edition.txt
#	forge-gui/res/formats/Type 1, Torment.txt
#	forge-gui/res/formats/Type 1.5, Apocalypse.txt
#	forge-gui/res/formats/Type 1.5, Apr 2003.txt
#	forge-gui/res/formats/Type 1.5, Darksteel.txt
#	forge-gui/res/formats/Type 1.5, Eighth Edition.txt
#	forge-gui/res/formats/Type 1.5, Fifth Dawn.txt
#	forge-gui/res/formats/Type 1.5, Invasion.txt
#	forge-gui/res/formats/Type 1.5, Jan 2002.txt
#	forge-gui/res/formats/Type 1.5, Jan 2004.txt
#	forge-gui/res/formats/Type 1.5, Judgment.txt
#	forge-gui/res/formats/Type 1.5, Jul 2003.txt
#	forge-gui/res/formats/Type 1.5, Legions.txt
#	forge-gui/res/formats/Type 1.5, Mercadian Masques.txt
#	forge-gui/res/formats/Type 1.5, Mirrodin.txt
#	forge-gui/res/formats/Type 1.5, Nemesis.txt
#	forge-gui/res/formats/Type 1.5, Oct 1999.txt
#	forge-gui/res/formats/Type 1.5, Oct 2000.txt
#	forge-gui/res/formats/Type 1.5, Odyssey.txt
#	forge-gui/res/formats/Type 1.5, Onslaught.txt
#	forge-gui/res/formats/Type 1.5, Planeshift.txt
#	forge-gui/res/formats/Type 1.5, Prophecy.txt
#	forge-gui/res/formats/Type 1.5, Scourge.txt
#	forge-gui/res/formats/Type 1.5, Sep 2004.txt
#	forge-gui/res/formats/Type 1.5, Seventh Edition.txt
#	forge-gui/res/formats/Type 1.5, Torment.txt
#	forge-gui/res/formats/Type 2, Chronicles.txt
#	forge-gui/res/formats/Type 2, Fourth Edition.txt
#	forge-gui/res/formats/Type 2, Ice Age.txt
#	forge-gui/res/formats/Un-Sets.txt
#	forge-gui/res/formats/Unlimited Edition.txt
#	forge-gui/res/formats/Urza Block Constructed, Apr 1999.txt
#	forge-gui/res/formats/Urza Block Constructed, Urza's Destiny.txt
#	forge-gui/res/formats/Urza Block Constructed, Urza's Legacy.txt
#	forge-gui/res/formats/Urza Block.txt
#	forge-gui/res/formats/Urza's Saga Block, Urza's Saga.txt
#	forge-gui/res/formats/Vintage, Alara Reborn.txt
#	forge-gui/res/formats/Vintage, Avacyn Restored.txt
#	forge-gui/res/formats/Vintage, Betrayers of Kamigawa.txt
#	forge-gui/res/formats/Vintage, Champions of Kamigawa.txt
#	forge-gui/res/formats/Vintage, Coldsnap.txt
#	forge-gui/res/formats/Vintage, Conflux.txt
#	forge-gui/res/formats/Vintage, Dark Ascension.txt
#	forge-gui/res/formats/Vintage, Dec 2004.txt
#	forge-gui/res/formats/Vintage, Dissension.txt
#	forge-gui/res/formats/Vintage, Eventide.txt
#	forge-gui/res/formats/Vintage, Future Sight.txt
#	forge-gui/res/formats/Vintage, Guildpact.txt
#	forge-gui/res/formats/Vintage, Innistrad.txt
#	forge-gui/res/formats/Vintage, July 2009.txt
#	forge-gui/res/formats/Vintage, Jun 2007.txt
#	forge-gui/res/formats/Vintage, Jun 2008.txt
#	forge-gui/res/formats/Vintage, Lorwyn.txt
#	forge-gui/res/formats/Vintage, Magic 2010.txt
#	forge-gui/res/formats/Vintage, Magic 2011.txt
#	forge-gui/res/formats/Vintage, Magic 2012.txt
#	forge-gui/res/formats/Vintage, Magic 2013.txt
#	forge-gui/res/formats/Vintage, Mar 2005.txt
#	forge-gui/res/formats/Vintage, Mirrodin Besieged.txt
#	forge-gui/res/formats/Vintage, Morningtide.txt
#	forge-gui/res/formats/Vintage, New Phyrexia.txt
#	forge-gui/res/formats/Vintage, Ninth Edition.txt
#	forge-gui/res/formats/Vintage, October 2011.txt
#	forge-gui/res/formats/Vintage, Planar Chaos.txt
#	forge-gui/res/formats/Vintage, Ravnica_ City of Guilds.txt
#	forge-gui/res/formats/Vintage, Rise of the Eldrazi.txt
#	forge-gui/res/formats/Vintage, Saviors of Kamigawa.txt
#	forge-gui/res/formats/Vintage, Scars of Mirrodin.txt
#	forge-gui/res/formats/Vintage, Sep 2004.txt
#	forge-gui/res/formats/Vintage, Sep 2005.txt
#	forge-gui/res/formats/Vintage, Sep 2007.txt
#	forge-gui/res/formats/Vintage, Sep 2008.txt
#	forge-gui/res/formats/Vintage, Shadowmoor.txt
#	forge-gui/res/formats/Vintage, Shards of Alara.txt
#	forge-gui/res/formats/Vintage, Tenth Edition.txt
#	forge-gui/res/formats/Vintage, Time Spiral.txt
#	forge-gui/res/formats/Vintage, Worldwake.txt
#	forge-gui/res/formats/Vintage, Zendikar.txt
#	forge-gui/res/formats/Zendikar Block Constructed, Rise of the Eldrazi.txt
#	forge-gui/res/formats/Zendikar Block Constructed, Worldwake.txt
#	forge-gui/res/formats/Zendikar Block Constructed, Zendikar.txt
#	forge-gui/res/formats/Zendikar Block.txt
2018-04-10 07:35:36 +01:00
austinio7116
a5d964a334 Updated formats to include subtype enum and made a first batch attempt to set the value
Now sorting formats by type, subtype then name
Added new "additional" property to gameformats to allow additional non printed but legal cards to be included e.g. dual lands in extended 1999
2018-04-10 07:28:31 +01:00
Hanmac
b5be110f0c Replacement Effect instead of StaticDamage 2018-04-10 07:15:41 +02:00
Hanmac
81ea744e38 fixed Druid's Deliverance 2018-04-10 07:15:26 +02:00
austinio7116
3e1f5c7632 Added set code validation check to gameformat parser 2018-04-09 23:35:08 +01:00
austinio7116
00a2a1ef4d Enabled historic/casual formats as starting pools for quest mode on desktop 2018-04-09 23:35:08 +01:00
maustin
ba793ac44e Renamed "Sanctioned format" to "Defined format" in quest starting pool options and enabled selecting from historic and other non-sanctioned formats in android quests 2018-04-09 23:35:08 +01:00
austinio7116
49dd3c0659 Modified format filter selection options to support large numbers of historic formats in the menu - uses a similar UI to the set selector 2018-04-09 23:35:08 +01:00
austinio7116
def7ba5faf Updates to UI to separate out format filters 2018-04-09 23:35:08 +01:00
austinio7116
e01eab131f Attempt to add other formats to format filter submenu on android 2018-04-09 23:35:08 +01:00
austinio7116
0636695052 Just use sanctioned formats where relevant, exclude historic formats where needed - may add these back later via new UI elements 2018-04-09 23:35:08 +01:00
austinio7116
1e893af9df Re-parsed historic formats to ensure AAA, BBB with correct spacing
Reindexed so order field increments only once between each format
2018-04-09 23:35:08 +01:00
austinio7116
0f5c6c0b0c Added individual formats from parsed historic formats.txt file 2018-04-09 23:35:08 +01:00
Michael Kamensky
f014285143 Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!370
2018-04-09 17:31:00 +00:00
Misha Colbourne
2f194a99d3 DOM: Added some cards 2018-04-09 16:00:31 +00:00
Michael Kamensky
63094bee7a Merge branch 'cube-update' into 'master'
- Update some ported draft cubes and add a new cube (Legacy 2018).

See merge request core-developers/forge!369
2018-04-09 09:00:15 +00:00
Agetian
7d2965ee34 - Update some ported draft cubes and add the February 2018 legacy cube. 2018-04-09 11:59:02 +03:00
Michael Kamensky
aabfb0ab1c Merge branch 'deckgenimprovements' into 'master'
Updated card deck generation files, plus fixed bug with recent generation code change

See merge request core-developers/forge!368
2018-04-09 08:43:09 +00:00
austinio7116
bd321edd20 Updated card deck generation files, plus fixed bug with recent generation code change 2018-04-09 09:31:58 +01:00
Michael Kamensky
334be5afc7 Merge branch 'CopyPermPopulate' into 'master'
CopyPerm: rewoke and update with Populate

See merge request core-developers/forge!367
2018-04-09 08:28:56 +00:00
Hanmac
0bc12e744e CopyPerm: rewoke and update with Populate 2018-04-09 07:50:44 +02:00
austinio7116
15772e20ac Merge branch 'vital-promo-sets' into 'master'
Vital promo sets

See merge request core-developers/forge!366
2018-04-09 05:24:26 +00:00
Rob Schnautz
75d3972196 Delete Promo set for Gatherer.txt 2018-04-08 20:47:06 +00:00
Rob Schnautz
e89d6f7de3 Update DragonCon 1994.txt 2018-04-08 20:36:56 +00:00
Rob Schnautz
1a5f79732a Add new file 2018-04-08 20:35:52 +00:00
Rob Schnautz
1e2077cf91 Add new file 2018-04-08 20:32:29 +00:00
Rob Schnautz
6dbdfe2fb4 Add new file 2018-04-08 20:31:55 +00:00
Rob Schnautz
e7702f97b0 Add new file 2018-04-08 20:31:15 +00:00
Rob Schnautz
e1c47289c9 Add new file 2018-04-08 20:30:30 +00:00
Michael Kamensky
2efa5f6f14 Merge branch 'assorted-fixes' into 'master'
- Added a missing reference to Chandra, Fire of Kaladesh.

See merge request core-developers/forge!365
2018-04-08 20:06:11 +00:00
Michael Kamensky
bb1ab54d13 Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!364
2018-04-08 20:06:09 +00:00
Agetian
f49bd65526 - Added a missing reference to Chandra, Fire of Kaladesh. 2018-04-08 23:04:58 +03:00
austinio7116
37624bec61 Added set code validation check to gameformat parser 2018-04-08 20:21:16 +01:00
austinio7116
b4bd18c185 Enabled historic/casual formats as starting pools for quest mode on desktop 2018-04-08 19:27:20 +01:00
Misha Colbourne
8bae5dd93b DOM: Added some cards 2018-04-08 18:03:34 +00:00
maustin
6e98c98c11 Renamed "Sanctioned format" to "Defined format" in quest starting pool options and enabled selecting from historic and other non-sanctioned formats in android quests 2018-04-08 16:27:31 +01:00
Michael Kamensky
37e4d49d3d Merge branch 'BrawlFormat' into 'master'
Fix to ensure randomly added cards in deck generation for commander/brawl are…

See merge request core-developers/forge!363
2018-04-08 06:40:15 +00:00
austinio7116
48f56beaab Modified format filter selection options to support large numbers of historic formats in the menu - uses a similar UI to the set selector 2018-04-08 07:22:34 +01:00
maustin
9cef0d752a Merge branch 'master' into historicformats 2018-04-08 06:22:25 +01:00
austinio7116
e0b7061ac3 Fix to ensure randomly added cards in deck generation for commander/brawl are not illegal repeat copies of the commander 2018-04-08 06:10:37 +01:00
Rob Schnautz
304aa4eeeb Update Dominaria.txt 2018-04-08 01:36:59 +00:00
Michael Kamensky
79b614e5c3 Merge branch 'FlipCoinAmount' into 'master'
FlipCoin: add Amount so Head and Tails abilities can use the amount

See merge request core-developers/forge!360
2018-04-07 18:38:34 +00:00
Michael Kamensky
af5b20fcd5 Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!362
2018-04-07 18:31:13 +00:00
Misha Colbourne
f30e21ab4b DOM: Added some cards 2018-04-07 17:47:08 +00:00
Hanmac
6c04084891 FlipCoin: add Amount so Head and Tails abilities can use the amount 2018-04-07 11:14:07 +02:00
swordshine
70adc36089 Merge branch 'master' into 'master'
- Fixed Josu Vess

See merge request core-developers/forge!359
2018-04-07 03:31:22 +00:00
swordshine
f6b85ce384 - Fixed Josu Vess 2018-04-07 11:30:27 +08:00
swordshine
cce114ce04 Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!358
2018-04-07 03:27:20 +00:00
swordshine
6ba0577270 Merge branch 'master' into 'master'
DOM: Added some cards

See merge request core-developers/forge!357
2018-04-07 02:47:14 +00:00
Misha Colbourne
ac0755c8d3 DOM: Added some cards 2018-04-07 00:20:17 +00:00
Rob Schnautz
03a17a88a1 Update Dominaria.txt 2018-04-06 21:37:58 +00:00
Misha Colbourne
f593cf4e09 DOM: Added some cards 2018-04-06 16:09:59 +00:00
Misha Colbourne
80d24dc8a1 DOM: Added some cards 2018-04-06 15:58:52 +00:00
Misha Colbourne
4b3ce9e701 DOM: Added some cards 2018-04-06 15:54:34 +00:00
Misha Colbourne
89c6ad5f9d Update rite_of_belzenlok.txt 2018-04-06 15:46:31 +00:00
Misha Colbourne
99e451005c Update memorial_to_glory.txt 2018-04-06 15:45:56 +00:00
Misha Colbourne
635c898016 DOM: Added some cards 2018-04-06 15:42:02 +00:00
Misha Colbourne
691c0501ea DOM: Added some cards 2018-04-06 14:58:04 +00:00
Misha Colbourne
7c8ddb2132 Update the_flame_of_keld.txt 2018-04-06 14:57:42 +00:00
Misha Colbourne
22ede8bf66 Update the_first_eruption.txt 2018-04-06 14:57:16 +00:00
Misha Colbourne
0cebf1552f DOM: Added some cards 2018-04-06 14:16:22 +00:00
Misha Colbourne
e8fb4f50b3 Update the_flame_of_keld.txt 2018-04-06 14:03:07 +00:00
Misha Colbourne
a5ebf637bd Update the_antiquities_war.txt 2018-04-06 14:02:51 +00:00
Misha Colbourne
6e4fac03ec DOM: Added some cards 2018-04-06 13:58:07 +00:00
Misha Colbourne
58f4c1ee7e DOM: Added some cards 2018-04-06 13:39:14 +00:00
swordshine
faec1f969c - Update some scripts 2018-04-06 20:06:09 +08:00
swordshine
5ab42eaa1d - Added Helm of the Host 2018-04-06 18:54:43 +08:00
swordshine
ccab7cd9e1 - Updated Jaya Ballard 2018-04-06 18:27:43 +08:00
swordshine
24d86684ef - Added Lich's Mastery 2018-04-06 16:55:51 +08:00
swordshine
633a1d644a - Added Damping Sphere 2018-04-06 16:05:08 +08:00
swordshine
59057e69a3 - Added Daring Archaeologist 2018-04-06 13:57:58 +08:00
swordshine
6ce5261dbd Merge branch 'master' into 'master'
- DOM: Added some cards

See merge request core-developers/forge!356
2018-04-06 05:55:18 +00:00
swordshine
3b501003ee - Added Dauntless Bodyguard 2018-04-06 13:23:04 +08:00
swordshine
8e87805689 Merge branch 'master' into 'master'
Added "UntilYourNextTurn" duration for AnimateAll effect

See merge request core-developers/forge!355
2018-04-06 05:15:02 +00:00
swordshine
33e7caf965 - DOM: Added Sylvan Awakening 2018-04-06 13:13:17 +08:00
swordshine
5af448c039 - Fixed Song of Freyalise 2018-04-06 13:02:10 +08:00
swordshine
542b3da250 Merge branch 'master' into 'master'
- Fixed two cards

See merge request core-developers/forge!354
2018-04-06 04:52:25 +00:00
swordshine
acdb17d28c - Fixed two cards 2018-04-06 12:51:15 +08:00
swordshine
f9e90f0b97 Merge branch 'dom-oracle-updates' into 'master'
Oracle updates (non-functional)

See merge request core-developers/forge!353
2018-04-06 04:48:08 +00:00
swordshine
4810ba6378 Merge branch 'master' into 'master'
- Fixed some cards

See merge request core-developers/forge!351
2018-04-06 04:46:49 +00:00
swordshine
e61c8d83f5 - Cleanup 2018-04-06 12:46:16 +08:00
swordshine
671340e12f Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!350
2018-04-06 04:42:39 +00:00
Misha Colbourne
3c7ac3156a DOM: Added some cards 2018-04-06 01:55:42 +00:00
Misha Colbourne
d9f2d17d15 DOM: Added some cards 2018-04-06 01:36:29 +00:00
Misha Colbourne
7d760fe65b Update the_mirari_conjecture.txt 2018-04-06 01:32:01 +00:00
Rob Schnautz
53f0cc6f21 Update Dominaria.txt 2018-04-06 01:21:07 +00:00
Misha Colbourne
0a16859c4d Update benalish_honor_guard.txt 2018-04-06 01:18:05 +00:00
Misha Colbourne
32795034b5 DOM: Added some cards 2018-04-06 01:17:48 +00:00
Rob Schnautz
e0b474d2d4 Update gilded_lotus.txt 2018-04-06 01:16:52 +00:00
Misha Colbourne
e82ee5ac14 Update the_mirari_conjecture.txt 2018-04-06 01:06:24 +00:00
Misha Colbourne
b4b9a4672d DOM: Added some cards 2018-04-06 01:05:58 +00:00
Misha Colbourne
c64b70be2e DOM: Added some cards 2018-04-06 00:43:17 +00:00
Misha Colbourne
cf95c37f39 DOM: Added some cards 2018-04-05 23:37:18 +00:00
Misha Colbourne
ca855da46e DOM: Added some cards 2018-04-05 22:56:34 +00:00
maustin
92be2a787a Merge branch 'master' into historicformats 2018-04-05 22:44:40 +01:00
Misha Colbourne
d166f0afea DOM: Added some cards 2018-04-05 19:37:08 +00:00
Misha Colbourne
ab1e21079f Update song_of_freyalise.txt 2018-04-05 19:23:41 +00:00
Misha Colbourne
4956dba6ea DOM: Added some cards 2018-04-05 19:23:08 +00:00
Misha Colbourne
c9701b3631 DOM: Added some cards 2018-04-05 19:14:50 +00:00
Misha Colbourne
f61e5d8e14 DOM: Added some cards 2018-04-05 19:01:57 +00:00
Misha Colbourne
773c3fd0d5 Update unwind.txt 2018-04-05 18:52:26 +00:00
Misha Colbourne
f0b743a7cf Update unwind.txt 2018-04-05 18:52:09 +00:00
Misha Colbourne
5453b8f051 DOM: Added some cards 2018-04-05 18:51:59 +00:00
Misha Colbourne
dbe440b3ee DOM: Added some cards 2018-04-05 18:49:47 +00:00
Misha Colbourne
b410285758 DOM: Added some cards 2018-04-05 16:53:51 +00:00
Misha Colbourne
a591fefcbc DOM: Added some cards 2018-04-05 16:27:06 +00:00
Misha Colbourne
d4fc353909 DOM: Added some cards 2018-04-05 15:55:24 +00:00
Misha Colbourne
8c81064453 DOM: Added some cards 2018-04-05 15:26:48 +00:00
Misha Colbourne
fd553e9940 Update ghitu_chronicler.txt 2018-04-05 15:24:26 +00:00
Misha Colbourne
872f411250 DOM: Added some cards 2018-04-05 15:15:38 +00:00
Misha Colbourne
0cd4993fb0 DOM: Added some cards 2018-04-05 15:09:47 +00:00
Misha Colbourne
582d4d19e1 DOM: Added some cards 2018-04-05 15:07:54 +00:00
Misha Colbourne
9a159bd672 DOM: Added some cards 2018-04-05 14:59:36 +00:00
Misha Colbourne
29a798e2a9 DOM: Added some cards 2018-04-05 14:38:58 +00:00
Misha Colbourne
ee4e95b7e6 DOM: Added some cards 2018-04-05 14:38:47 +00:00
Misha Colbourne
11a04ffc15 DOM: Added some cards 2018-04-05 14:38:31 +00:00
Misha Colbourne
0391bf1cce DOM: Added some cards 2018-04-05 14:38:22 +00:00
Misha Colbourne
43d53ec2b4 DOM: Added some cards 2018-04-05 14:38:09 +00:00
swordshine
b44bf4bb47 - Fixed Etali, Primal Storm 2018-04-05 22:33:46 +08:00
swordshine
ecac92d9b3 - Skipping your "next" untap step is cumulative. 2018-04-05 22:16:54 +08:00
swordshine
45b6f1a6ef - Fixed "precombat main phase" triggers 2018-04-05 22:07:15 +08:00
swordshine
d61aeb09c0 - Fixed Sage of Ancient Lore 2018-04-05 21:28:15 +08:00
Misha Colbourne
8fa1843d79 DOM: Added some cards 2018-04-05 13:27:14 +00:00
Misha Colbourne
3ee7277409 DOM: Added some cards 2018-04-05 13:17:28 +00:00
swordshine
1139940c24 Merge branch 'master' into 'master'
- Fixed cost description for Cycling (Street Wraith)

See merge request core-developers/forge!349
2018-04-05 13:16:20 +00:00
swordshine
571e69bb16 - Fixed Land Tax and similar cards 2018-04-05 21:15:53 +08:00
swordshine
a5ea35e6b7 - Fixed cost description for Cycling (Street Wraith) 2018-04-05 20:18:21 +08:00
swordshine
d4ad66e2a5 Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!348
2018-04-05 02:32:34 +00:00
Misha Colbourne
886c732626 DOM: Added some cards 2018-04-05 01:06:49 +00:00
Misha Colbourne
c4e0528ce8 DOM: Added some cards 2018-04-05 00:56:15 +00:00
Misha Colbourne
d4f524769f Update cabal_paladin.txt 2018-04-05 00:40:54 +00:00
Rob Schnautz
defdeb1f44 Update Dominaria.txt 2018-04-04 23:55:40 +00:00
Rob Schnautz
3b411c657d Update Dominaria.txt 2018-04-04 23:32:14 +00:00
Misha Colbourne
67037c7837 DOM: Added some new cards 2018-04-04 22:59:42 +00:00
Misha Colbourne
dd93588539 DOM: Added some new cards 2018-04-04 22:07:30 +00:00
Misha Colbourne
d7bc7df3d2 DOM: Added some new cards 2018-04-04 22:07:06 +00:00
maustin
49d0950a74 Merge branch 'master' into historicformats 2018-04-04 23:01:24 +01:00
swordshine
b3aae7824c Merge branch 'sagaCounterFix' into 'master'
Saga: fix if last ability is countered

See merge request core-developers/forge!347
2018-04-04 10:59:07 +00:00
Hanmac
e0dc5d4ced Saga: fix if last ability is countered 2018-04-04 07:49:04 +02:00
Sol
f7099b4555 Merge branch 'network-play' into 'master'
use CardView for selection of exterting attackers

See merge request core-developers/forge!345
2018-04-04 02:37:47 +00:00
Jamin W. Collins
3f8a3f3342 use CardView for selection of exterting attackers
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-04-03 20:12:15 -06:00
swordshine
2b8afc3972 Merge branch 'patch' into 'master'
Fixed a crash when Momir Avatar copys an etbcounter card

See merge request core-developers/forge!344
2018-04-04 01:03:33 +00:00
swordshine
771341f2e9 - Fixed a crash when MMomir Avatar copys an etbcounter card 2018-04-04 09:02:35 +08:00
Michael Kamensky
8c928e2b01 Merge branch 'saga' into 'master'
Do Saga Abilities

See merge request core-developers/forge!343
2018-04-03 17:27:54 +00:00
austinio7116
c7139c1da1 Updates to UI to separate out format filters 2018-04-03 17:27:42 +01:00
austinio7116
0338cc4458 Attempt to add other formats to format filter submenu on android 2018-04-03 14:40:29 +01:00
Hanmac
8c601f86f6 Do Saga Abilities 2018-04-03 15:19:54 +02:00
austinio7116
eeae378d69 Just use sanctioned formats where relevant, exclude historic formats where needed - may add these back later via new UI elements 2018-04-03 09:44:58 +01:00
austinio7116
f1fc4013c2 Re-parsed historic formats to ensure AAA, BBB with correct spacing
Reindexed so order field increments only once between each format
2018-04-03 06:14:05 +01:00
austinio7116
d8dafbfac3 Added individual formats from parsed historic formats.txt file 2018-04-03 06:07:18 +01:00
Michael Kamensky
4537c364ec Merge branch 'bugfixes2' into 'master'
Catch UPNP error on android to allow hosting network game

See merge request core-developers/forge!341
2018-04-03 04:45:12 +00:00
Rob Schnautz
0eb66aebff Update Dominaria.txt 2018-04-03 03:51:30 +00:00
Sol
eb4fc9f7ce Merge branch 'momirvariant' into 'master'
Added MoJhoSto variant

See merge request core-developers/forge!338
2018-04-03 01:14:19 +00:00
austinio7116
e6ddc61e86 Catch UPNP error on android to allow hosting network game 2018-04-02 23:31:06 +01:00
Sol
c021fd5eb8 Merge branch 'bugfixes2' into 'master'
Readded extended format to fix quests

See merge request core-developers/forge!340
2018-04-02 21:34:59 +00:00
austinio7116
9b89e79f44 Readded extended format 2018-04-02 22:23:16 +01:00
austinio7116
79ed54097a Readded extended format to fix quests 2018-04-02 22:19:46 +01:00
austinio7116
8647409f9d Readded extended format 2018-04-02 22:03:57 +01:00
Michael Kamensky
03d9983924 Merge branch 'cardedition-npe' into 'master'
Add null check on CardEdition

See merge request core-developers/forge!339
2018-04-02 20:21:05 +00:00
Michael Kamensky
dbd65941dc Merge branch 'patch' into 'master'
- Fixed a bug (Yisan, the Wanderer Bard's + Rings of Brighthearth)

See merge request core-developers/forge!337
2018-04-02 20:21:00 +00:00
Michael Kamensky
b481b629b5 Update CardEdition.java 2018-04-02 20:20:41 +00:00
Luke Way
0b5a92ebc9 Fixed whitespace 2018-04-02 09:36:58 -04:00
Luke Way
942f5dcfbd Added null check on CardEdition. Encountered NPE when adding basic lands in deck builder with cards from new set. 2018-04-02 09:32:38 -04:00
austinio7116
a73667610b Added MoJhoSto variant 2018-04-02 07:19:09 +01:00
swordshine
0e98da8bca - Yisan, the Wanderer Bard's + Rings of Brighthearth now works 2018-04-02 13:54:09 +08:00
Michael Kamensky
4dcd80e2ef Merge branch 'patch' into 'master'
- Fixed a crash (varolz, the scar-striped + dryad arbor)

See merge request core-developers/forge!336
2018-04-02 05:50:55 +00:00
swordshine
eeec3feb1f - Fixed a crash (varolz, the scar-striped + dryad arbor) 2018-04-02 11:55:44 +08:00
Sol
af70a1f58b Merge branch 'BrawlFormat' into 'master'
Moved formats into separate folder so that custom formats can be added

See merge request core-developers/forge!331
2018-04-01 02:53:55 +00:00
Sol
8ebd6e082e Merge branch 'user-reported-bugs' into 'master'
Crackling Doom - call logic for creature sacrifice

Closes #401

See merge request core-developers/forge!335
2018-04-01 02:27:34 +00:00
austinio7116
ff1e6b835c Fixed label of rhonas's last stand effect 2018-03-31 22:02:37 +01:00
austinio7116
f76992d9de Updated gameformats with new enum type to separate sanctioned, casual, historic and custom. 2018-03-31 21:41:37 +01:00
austinio7116
d11013c689 Updated gameformats with new enum type to separate sanctioned, casual, historic and custom. 2018-03-31 21:23:22 +01:00
Jamin W. Collins
4b7a020f8e Crackling Doom - call logic for creature sacrifice
Fixes: core-developers/forge#401

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-31 10:46:32 -06:00
swordshine
a7740757a9 Merge branch 'patch' into 'master'
- Fixed Evra, Halcyon Witness

See merge request core-developers/forge!334
2018-03-31 14:40:56 +00:00
swordshine
e59477801e - Fixed Evra, Halcyon Witness 2018-03-31 22:40:29 +08:00
austinio7116
81326c996d Updated CHANGES.txt 2018-03-31 07:09:56 +01:00
austinio7116
9fbac2aeaa Updated CHANGES.txt 2018-03-31 07:08:46 +01:00
austinio7116
6248cf31c3 Code and properties cleanup 2018-03-31 06:55:01 +01:00
maustin
f3d11dbe5b Merge branch 'master' into BrawlFormat 2018-03-30 22:07:13 +01:00
austinio7116
a8c5d7bf2a Merge branch 'network-play' into 'master'
Network play fixes

Closes #396

See merge request core-developers/forge!333
2018-03-30 21:06:05 +00:00
austinio7116
c78d235b07 Moved formats into single files with new "Order", "Core" and "Name" properties. 2018-03-30 21:57:13 +01:00
Jamin W. Collins
2f5a83e171 Show external IP when hosting, if possible
Fixes: core-developers/forge#396

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-30 08:37:17 -06:00
Jamin W. Collins
35d0e73901 handle null response from getPhase()
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-30 08:20:34 -06:00
Jamin W. Collins
434573a5f8 reflow the PhaseIndicator switch statement
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-30 08:11:50 -06:00
austinio7116
3810475ee8 Better example format files to incude pauper and an example block format 2018-03-30 10:08:08 +01:00
austinio7116
057f5face3 Fixed folder format loading to ensure core sets load first (otherwise android may not load standard correctly) 2018-03-30 09:53:35 +01:00
maustin
021c133db3 Merge branch 'master' into BrawlFormat 2018-03-30 08:06:39 +01:00
austinio7116
9082a89c59 Moved formats into separate folder so that additional custom format files can be added - added Pauper and Kaladesh_Standard as examples 2018-03-30 07:49:54 +01:00
Michael Kamensky
70eec78416 Merge branch 'BrawlFormat' into 'master'
Brawl format

See merge request core-developers/forge!324
2018-03-30 06:35:07 +00:00
Michael Kamensky
4e824ae7d2 Merge branch 'adjust-upcoming-set-handling' into 'master'
Only allow core or expansions to be upcoming sets

See merge request core-developers/forge!329
2018-03-30 05:59:38 +00:00
Rob Schnautz
1bac0903a0 Update Dominaria.txt 2018-03-30 02:37:48 +00:00
Rob Schnautz
d0ddadeb13 Add new file 2018-03-30 02:25:26 +00:00
Chris H
4b77ccb5f6 Only allow core or expansions to be upcoming sets 2018-03-29 21:29:07 -04:00
austinio7116
3ea9f7d218 Removed unwanted whitespace and imports 2018-03-29 21:06:41 +01:00
Michael Kamensky
84a49752d0 Merge branch 'network-play' into 'master'
Network Play Fixes

Closes #390

See merge request core-developers/forge!327
2018-03-29 20:02:42 +00:00
Jamin Collins
736513f8e1 Merge branch 'cantTgtNullPointerNoValidTgt' into 'master'
Prevent crash when no validTgts in params for spell ability

See merge request core-developers/forge!328
2018-03-29 15:29:04 +00:00
austinio7116
e26887036e Now using filterrules rather than printing rules to determine if a card is brawl legal - still using printing rules to determine which basic lands to use in generated decks 2018-03-29 16:28:32 +01:00
Jamin W. Collins
e9773e57b7 make dual list dialog sizing dynamic, like single
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-29 09:26:53 -06:00
Jamin W. Collins
1d5a3b0184 ensure that SAVs are used for player decision
Fixes: core-developers/forge#390

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-29 09:26:53 -06:00
Jamin W. Collins
f6d8a85423 remove trailing whitespace
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-29 09:26:53 -06:00
austinio7116
8e074e2093 Removed unwanted import 2018-03-29 11:15:06 +01:00
austinio7116
70bd88e2d9 Fixed failing tests due to missing StaticData. Fixed Commander-based fully random deck generation. 2018-03-29 08:47:15 +01:00
austinio7116
9e053865e1 Code cleanup 2018-03-29 07:00:37 +01:00
austinio7116
08dc5f5654 Fixed deckformat to fully support Brawl requiring standard legal cards. Deck generation for brawl working on desktop and android. 2018-03-29 06:33:02 +01:00
elliot
a1d7f2c774 Prevent crash when no validTgts in params for static ability
Issue reproducable casting chandra's ignition into a board with only a hexproof creature
2018-03-28 21:32:25 -04:00
austinio7116
ed564b904f Random brawl deck generator working on Desktop 2018-03-28 23:41:57 +01:00
maustin
b9aef9b317 Merge branch 'master' into BrawlFormat 2018-03-28 06:44:42 +01:00
Jamin W. Collins
7b9bfaaa18 ensure WrappedAbility has reasonable description set
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-27 17:39:12 -06:00
austinio7116
edf6d48e6b Remove commander damage rule from Brawl 2018-03-28 00:01:39 +01:00
austinio7116
ba41a27981 Corrected name of brawl deck builder button 2018-03-27 23:53:27 +01:00
maustin
f72ca1d2ae Merge remote-tracking branch 'origin/BrawlFormat' into BrawlFormat 2018-03-27 23:05:24 +01:00
austinio7116
e2ee43fb2d Code cleanup 2018-03-27 23:04:16 +01:00
austinio7116
6a38654a23 Code cleanup 2018-03-27 06:50:39 +01:00
Michael Kamensky
3e9e1ee865 Merge branch 'dom-oracle-updates' into 'master'
DOM Oracle changes (non-functional)

See merge request core-developers/forge!325
2018-03-27 05:07:01 +00:00
Sol
eb12b7a7ec Merge branch 'quotefix' into 'master'
Use double quotes to prevent users with apostrophes from causing issues.

Closes #385

See merge request core-developers/forge!326
2018-03-27 01:43:20 +00:00
KrazyTheFox
2d23ce0084 Use double quotes to prevent users with apostrophes from causing issues.
Pesky users.
2018-03-26 21:40:31 -04:00
swordshine
b94aa70c65 Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!323
2018-03-27 00:57:59 +00:00
Rob Schnautz
0890dcee95 Update woodland_cemetery.txt 2018-03-27 00:40:58 +00:00
Rob Schnautz
6cd44b4564 Update sulfur_falls.txt 2018-03-27 00:40:25 +00:00
Rob Schnautz
54a56a8cd4 Update isolated_chapel.txt 2018-03-27 00:39:30 +00:00
Rob Schnautz
80552bc2f1 Update hinterland_harbor.txt 2018-03-27 00:38:32 +00:00
Rob Schnautz
7cc8e606f1 Update clifftop_retreat.txt 2018-03-27 00:37:59 +00:00
Rob Schnautz
eaaf52ae3d Update timber_gorge.txt 2018-03-27 00:36:26 +00:00
Rob Schnautz
e8dc787d8c Update meandering_river.txt 2018-03-27 00:35:48 +00:00
Misha Colbourne
c8a213ce86 Update teferi_timebender.txt 2018-03-26 23:15:43 +00:00
Misha Colbourne
0a1a4a4ad1 DOM: Added some cards 2018-03-26 23:08:40 +00:00
Misha Colbourne
32644f17fe DOM: Added some cards 2018-03-26 22:54:27 +00:00
Misha Colbourne
cc3196da95 Update karplusan_hound.txt 2018-03-26 22:53:18 +00:00
Misha Colbourne
e8e854cfa2 DOM: Added some cards 2018-03-26 22:48:49 +00:00
Misha Colbourne
1830eb7186 Update niambi_faithful_healer.txt 2018-03-26 22:45:00 +00:00
Misha Colbourne
d5f93b722c DOM: Added some cards 2018-03-26 22:37:14 +00:00
Misha Colbourne
7271b6f585 DOM: Added some cards 2018-03-26 22:01:30 +00:00
austinio7116
5b8342188c Fixed adding planeswalker commanders from pool in android brawl deck editor 2018-03-26 22:59:16 +01:00
Misha Colbourne
acae61c26a DOM: Added some cards 2018-03-26 21:58:53 +00:00
austinio7116
756c272821 Fixed brawl deck editor and deckformat rules for brawl 2018-03-26 20:39:34 +01:00
Michael Kamensky
d71026f7ae Merge branch 'patch' into 'master'
- Reset the activations so that flickering a planeswalker can activate the loyalty ability again

See merge request core-developers/forge!322
2018-03-26 18:16:15 +00:00
Misha Colbourne
71edc398d0 DOM: Added some cards 2018-03-26 17:34:24 +00:00
swordshine
ab96026176 - Reset the activations so that flickering a planeswalker can activate the loyalty ability again 2018-03-26 22:47:45 +08:00
swordshine
5942df216a Merge branch 'apiTypeCaseCrashFix' into 'master'
Api type case crash fix

See merge request core-developers/forge!321
2018-03-26 14:45:33 +00:00
swordshine
319b450532 Merge branch 'patch' into 'master'
Patch

See merge request core-developers/forge!319
2018-03-26 14:44:22 +00:00
austinio7116
364bf3724f Added UI elements required for Brawl variant 2018-03-26 06:57:15 +01:00
elliot
45cfc56e2d Merge branch 'master' of https://git.cardforge.org/core-developers/forge into apiTypeCaseCrashFix 2018-03-25 23:20:39 -04:00
elliot
b40f460c59 Simple fix for issue where the case of lifeloss for afflict creatures did not match apiType case, causing crashes 2018-03-25 23:08:06 -04:00
Sol
c980d1c9d4 Merge branch 'sysout-cleanup' into 'master'
Sysout cleanup

See merge request core-developers/forge!286
2018-03-26 01:18:19 +00:00
swordshine
f961bad4d9 Merge branch 'patch' into 'master'
Update swift_warden.txt

See merge request core-developers/forge!320
2018-03-26 00:40:56 +00:00
Misha Colbourne
a0626b7166 Update swift_warden.txt 2018-03-25 22:12:46 +00:00
austinio7116
81373d2b69 Initial fiddling with Brawl game formats 2018-03-25 19:58:52 +01:00
swordshine
18a0ec2f67 - Fixed Bloodstone Goblin 2018-03-25 23:11:36 +08:00
swordshine
1009a73e0a - Added some cards 2018-03-25 22:49:40 +08:00
swordshine
318377cb7e - Deepglow Skate should work with Doubling Season 2018-03-25 22:44:21 +08:00
Michael Kamensky
a8ebd94e88 Merge branch 'crested-sunmare-modifier' into 'master'
Make AI remove Crested Sunmare before its tokens

See merge request core-developers/forge!318
2018-03-25 14:21:30 +00:00
swordshine
3af07078c1 - Added Naban, Dean of Iteration (the Changetext effect was tested) 2018-03-25 22:07:29 +08:00
Luke Way
b917d242ea Removed import 2018-03-25 09:04:39 -04:00
Luke Way
393eb54794 Removed debug statement entirely 2018-03-25 09:03:38 -04:00
Luke Way
c58d274626 Added AIEvaluationModifier for Crested Sunmare so AI removes sunmare before its tokens 2018-03-25 08:49:04 -04:00
swordshine
c6ce1ef87f Merge branch 'patch' into 'master'
- Added some new cards

See merge request core-developers/forge!317
2018-03-25 08:41:29 +00:00
swordshine
489b1a0eb4 - Added some new cards 2018-03-25 16:39:41 +08:00
swordshine
ea3ddee83a Merge branch 'castThisWay' into 'master'
castThisWay for Kess

See merge request core-developers/forge!316
2018-03-25 07:13:44 +00:00
swordshine
e80acafc0e Merge branch 'ddu-functional-updates' into 'master'
DDU functional updates

See merge request core-developers/forge!309
2018-03-25 07:13:36 +00:00
Rob Schnautz
1619b422b8 Functional update: "the player or planeswalker it's attacking" 2018-03-24 19:35:56 +00:00
Hanmac
5b159a0e64 CardProperty: add CastSA to card
ForgeScript: add mayPlaySource
2018-03-24 18:00:09 +01:00
Michael Kamensky
b6e80a9e3f Merge branch 'fix-fertile-ground-on-nonbasics' into 'master'
When copying costs, make sure to recache the TapCost state.

See merge request core-developers/forge!315
2018-03-24 05:15:06 +00:00
Chris H
8e15326b0f When copying costs, make sure to recache the TapCost state. 2018-03-24 00:03:57 -04:00
Sol
edca8e15e1 Merge branch 'apitype-static-init' into 'master'
ApiType Tweaks

See merge request core-developers/forge!287
2018-03-24 00:26:10 +00:00
Michael Kamensky
3c90fba52e Merge branch 'assorted-fixes' into 'master'
Added Indestructible to the Keyword enum, updated CHANGES.txt.

See merge request core-developers/forge!314
2018-03-23 08:32:29 +00:00
Agetian
dd45035cdc - Updating CHANGES.txt. 2018-03-23 11:26:04 +03:00
Agetian
7010e9ffd3 - Added Indestructible to the Keyword enum. 2018-03-23 11:22:54 +03:00
swordshine
54af6a5cdb Merge branch 'counterAddRemoveUpdate' into 'master'
Counter Effects: update Last State

See merge request core-developers/forge!313
2018-03-23 07:28:56 +00:00
Hanmac
8316c9cffa Counter Effects: update Last State
Tribute: use LKI canReceiveCounters for Tribute check
2018-03-23 07:37:59 +01:00
swordshine
6d9772fa9c Merge branch 'patch' into 'master'
- Fixed two cards

See merge request core-developers/forge!312
2018-03-23 04:39:59 +00:00
swordshine
564a799ebf - Fixed two cards 2018-03-23 12:39:33 +08:00
swordshine
c95c915de7 Merge branch 'patch' into 'master'
DOM: Added some cards

See merge request core-developers/forge!305
2018-03-23 04:14:06 +00:00
swordshine
7b398459db Merge branch 'jace' into 'master'
SS1 Oracle update

See merge request core-developers/forge!311
2018-03-23 04:13:21 +00:00
swordshine
306b4e6a56 Merge branch 'ddu' into 'master'
DDU Edition file and Oracle updates (non-functional)

See merge request core-developers/forge!308
2018-03-23 04:13:04 +00:00
swordshine
d425e7678a Merge branch 'counterMoveUpdate' into 'master'
CounterMoveEffect: update Last State source and target

See merge request core-developers/forge!310
2018-03-23 04:12:15 +00:00
swordshine
1bd18eece0 Merge branch 'master' into 'master'
Support playing against Net Decks through the Test Deck function on mobile Forge.

See merge request core-developers/forge!307
2018-03-23 04:12:02 +00:00
Misha Colbourne
674c84f90a DOM: Added some cards 2018-03-23 01:26:16 +00:00
Misha Colbourne
d4584c47c2 DOM: Added some cards 2018-03-23 01:13:13 +00:00
Misha Colbourne
cffe0d3e80 DOM: Added some cards 2018-03-23 01:02:32 +00:00
Misha Colbourne
be4452ec59 DOM: Added some cards 2018-03-23 00:54:32 +00:00
Misha Colbourne
8a4b27d8ae DOM: Added some cards 2018-03-23 00:48:17 +00:00
Rob Schnautz
8a34c8330f Update jace_beleren.txt 2018-03-22 22:06:24 +00:00
Hanmac
7bd0161243 CounterMoveEffect: update Last State source and target 2018-03-22 07:50:32 +01:00
Misha Colbourne
5bf30ba4d2 DOM: Added some cards 2018-03-22 03:03:50 +00:00
Misha Colbourne
9be00788fb DOM: Added some cards 2018-03-22 02:25:02 +00:00
Misha Colbourne
253082f396 DOM: Added some cards 2018-03-22 01:50:28 +00:00
Misha Colbourne
7c4d4519a6 DOM: Added some cards 2018-03-22 01:36:04 +00:00
Rob Schnautz
07b0169f8e Update temple_of_epiphany.txt 2018-03-22 01:01:51 +00:00
Rob Schnautz
14f3d77e7e Update swiftwater_cliffs.txt 2018-03-22 01:00:54 +00:00
Rob Schnautz
f5668478d5 Update shivan_reef.txt 2018-03-22 01:00:08 +00:00
Rob Schnautz
17896501cf Update seat_of_the_synod.txt 2018-03-22 00:58:57 +00:00
Rob Schnautz
21835f574d Update phyrexias_core.txt 2018-03-22 00:58:04 +00:00
Rob Schnautz
9b029280fd Update great_furnace.txt 2018-03-22 00:56:54 +00:00
Rob Schnautz
4e1ff6af7d Update foundry_of_the_consul.txt and fix filename 2018-03-22 00:55:30 +00:00
Rob Schnautz
e339c9b0a9 Update darksteel_citadel.txt 2018-03-22 00:50:00 +00:00
Rob Schnautz
38dc30f9b9 Update plains.txt 2018-03-22 00:48:25 +00:00
Rob Schnautz
d291965a58 Update island.txt 2018-03-22 00:48:03 +00:00
Rob Schnautz
edb1d697fe Update swamp.txt 2018-03-22 00:47:40 +00:00
Rob Schnautz
8b2fbc409c Update mountain.txt 2018-03-22 00:47:15 +00:00
Rob Schnautz
4025c31570 Update forest.txt 2018-03-22 00:46:44 +00:00
Rob Schnautz
7518fa2bf3 Update treetop_village.txt 2018-03-22 00:44:17 +00:00
Rob Schnautz
aa830919db Update tranquil_thicket.txt 2018-03-22 00:41:34 +00:00
Rob Schnautz
2a91d26728 Update oran_rief_the_vastwood.txt 2018-03-22 00:40:39 +00:00
Rob Schnautz
b4de00da0b Update scuttling_doom_engine.txt 2018-03-22 00:35:42 +00:00
Rob Schnautz
6c8a00df5d Update pyrite_spellbomb.txt 2018-03-22 00:34:27 +00:00
Rob Schnautz
f109575ed1 Functional change is required on line 9. See Issue #370 2018-03-22 00:32:32 +00:00
Rob Schnautz
0bd06961d9 Update talaras_battalion.txt 2018-03-22 00:17:59 +00:00
Rob Schnautz
bc40d17e5a Update leaf_gilder.txt 2018-03-22 00:14:54 +00:00
Rob Schnautz
becdb36e84 Update krosan_tusker.txt 2018-03-22 00:13:10 +00:00
Rob Schnautz
1e7cd00e2d Update elvish_mystic.txt 2018-03-22 00:10:14 +00:00
Rob Schnautz
7e44663112 Update elvish_archdruid.txt 2018-03-22 00:09:15 +00:00
Rob Schnautz
3053186479 Update elvish_aberration.txt 2018-03-22 00:08:17 +00:00
Rob Schnautz
b0c0cecb05 Update shrapnel_blast.txt 2018-03-22 00:05:44 +00:00
Rob Schnautz
58a897dd6d Update pia_and_kiran_nalaar.txt 2018-03-22 00:04:13 +00:00
Rob Schnautz
28023c7530 Update barrage_ogre.txt 2018-03-22 00:01:47 +00:00
Rob Schnautz
f978d94c4b Update galvanic_blast.txt 2018-03-22 00:00:44 +00:00
Rob Schnautz
66d0ef00b3 Update galvanic_blast.txt 2018-03-22 00:00:06 +00:00
Misha Colbourne
eea735f4b2 DOM: Added some cards 2018-03-21 23:58:12 +00:00
Rob Schnautz
55287f9385 FUNCTIONAL UPDATE 2018-03-21 23:56:55 +00:00
Rob Schnautz
851c86d032 Update trinket_mage.txt 2018-03-21 23:53:20 +00:00
Rob Schnautz
10ff95fa6b Update treasure_mage.txt 2018-03-21 23:52:08 +00:00
Rob Schnautz
f1b2c78695 Update Duel Decks Elves vs. Inventors.txt 2018-03-21 23:42:44 +00:00
Misha Colbourne
303e7c74c3 DOM: Added some cards 2018-03-21 23:14:19 +00:00
Misha Colbourne
27a6313ce1 DOM: Added some cards 2018-03-21 18:47:03 +00:00
Michael Kamensky
7d39889cef Merge branch 'androidmultiaiplayers' into 'master'
3/4 player matches on Android

See merge request core-developers/forge!295
2018-03-21 18:20:22 +00:00
Misha Colbourne
afd1c6513f DOM: Added some cards 2018-03-21 17:03:27 +00:00
Misha Colbourne
ec86ca0fdd DOM: Added some cards 2018-03-21 16:56:43 +00:00
Misha Colbourne
4e0d799d98 DOM: Added some cards 2018-03-21 16:51:21 +00:00
Misha Colbourne
fed16e5be9 DOM: Added some cards 2018-03-21 04:39:46 +00:00
Misha Colbourne
5a50cd3c16 DOM: Added some cards 2018-03-21 04:20:59 +00:00
Alexei Svitkine
f436fa895b Support playing against Net Decks through the Test Deck function
on mobile Forge.
2018-03-21 00:04:14 -04:00
swordshine
fa66720fc7 Merge branch 'jace' into 'master'
Signature Spellbook: Jace

See merge request core-developers/forge!306
2018-03-21 03:51:25 +00:00
Rob Schnautz
a7bb112c70 SS1 edition file 2018-03-21 01:52:51 +00:00
Misha Colbourne
d117e34a79 DOM: Added some cards 2018-03-21 01:52:13 +00:00
Misha Colbourne
52e103d9b3 DOM: Added some cards 2018-03-21 01:40:08 +00:00
Misha Colbourne
0bedf7a901 DOM: Added some cards 2018-03-21 01:13:34 +00:00
Misha Colbourne
04ec7965c1 DOM: Added some cards 2018-03-21 01:04:37 +00:00
Misha Colbourne
789ed72689 DOM: Added some cards 2018-03-21 00:44:18 +00:00
Misha Colbourne
eebb1adbd8 DOM: Added some cards 2018-03-20 23:30:04 +00:00
Misha Colbourne
d4d75684b4 DOM: Added some cards 2018-03-20 23:03:14 +00:00
austinio7116
951a5dc978 Enabled team combo box on android now that multiplayer is possible, also attempted to prevent hanging on conceding in multiplayer games - only working on android so far 2018-03-20 21:53:06 +00:00
Michael Kamensky
7fab48ba9b Merge branch 'mobile-release' into 'master'
Preparing Forge for Android publish 1.6.8.001 [incremental].

See merge request core-developers/forge!304
2018-03-20 06:12:06 +00:00
Agetian
b2088e7cd2 - Preparing Forge for Android publish 1.6.8.001 [incremental]. 2018-03-20 09:10:54 +03:00
Blacksmith
04a39c34c6 Clear out release files in preparation for next release 2018-03-20 01:19:56 +00:00
Blacksmith
9e786bbdff [maven-release-plugin] prepare for next development iteration 2018-03-20 01:14:26 +00:00
Blacksmith
6e6af1c6c4 [maven-release-plugin] prepare release forge-1.6.8 2018-03-20 01:14:23 +00:00
Blacksmith
394c048b21 Update README.txt for release 2018-03-20 01:12:53 +00:00
austinio7116
94e255b198 Fix for broken variants with new android multiplayer code 2018-03-19 12:52:49 +00:00
Michael Kamensky
8a41f8f476 Merge branch 'generated-stack-text' into 'master'
improve the generated stack text for Brainstorm

Closes #350

See merge request core-developers/forge!303
2018-03-19 06:04:58 +00:00
Sol
c680df8738 Merge branch 'multiplayer-platinum-angel' into 'master'
Improve can't win/lose effects for multiplayer

Closes #170

See merge request core-developers/forge!302
2018-03-18 21:26:19 +00:00
Jamin W. Collins
49f050655c improve the generated stack text for Brainstorm
Fixes: core-developers/forge#350

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-18 15:21:15 -06:00
Jamin W. Collins
f270213bb7 Improve can't win/lose effects for multiplayer
Fixes: core-developers/forge#170

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-18 13:38:49 -06:00
Michael Kamensky
6ed064eca6 Merge branch 'assorted-fixes' into 'master'
Ethereal Armor: added a missing SVar reference.

See merge request core-developers/forge!301
2018-03-18 17:18:17 +00:00
Agetian
d37f9a6ea1 - Ethereal Armor: added a missing SVar reference. 2018-03-18 20:16:28 +03:00
Michael Kamensky
ea7121f9d9 Merge branch 'newHexproof' into 'master'
Hexproof: is now done as CantTarget

See merge request core-developers/forge!300
2018-03-18 17:05:44 +00:00
Hanmac
9af8fddaf3 Hexproof: is now done as CantTarget with special shared handling 2018-03-18 15:31:19 +01:00
swordshine
b81fba82a5 Merge branch 'SellingLandsReplacement' into 'master'
Selling lands fixes

See merge request core-developers/forge!273
2018-03-18 09:17:52 +00:00
swordshine
fc220206d7 Merge branch 'patch' into 'master'
DOM: Fixed some scripts

See merge request core-developers/forge!299
2018-03-18 09:10:02 +00:00
swordshine
2686318c11 - DOM: Fixed some scripts 2018-03-18 17:08:47 +08:00
swordshine
869d32785d Merge branch 'cleanup-game-state' into 'master'
Clean up GameState code

See merge request core-developers/forge!297
2018-03-18 09:01:21 +00:00
swordshine
1e14373438 Merge branch 'patch' into 'master'
DOM: Yet more new cards

See merge request core-developers/forge!298
2018-03-18 08:55:03 +00:00
swordshine
e637874b85 Merge branch 'assorted-fixes' into 'master'
Fixed Doomsday (the search effect was optional/arbitrary) and Stangg (the twin token was not exiled).

Closes #356 and #359

See merge request core-developers/forge!296
2018-03-18 08:54:46 +00:00
Misha Colbourne
0a1efba7d9 DOM: Yet more new cards 2018-03-18 00:32:07 +00:00
Misha Colbourne
e7dca7ff6f Update whisper_blood_liturgist.txt 2018-03-18 00:30:22 +00:00
Misha Colbourne
93b552b33c DOM: Yet more new cards 2018-03-18 00:09:28 +00:00
Misha Colbourne
59a36015b6 DOM: Yet more new cards 2018-03-17 23:56:42 +00:00
Misha Colbourne
1ccf93b64b DOM: Yet more new cards 2018-03-17 23:37:39 +00:00
Misha Colbourne
b702effb60 DOM: Yet more new cards 2018-03-17 23:14:46 +00:00
Misha Colbourne
882218b493 DOM: Yet more new cards 2018-03-17 23:09:07 +00:00
Misha Colbourne
2ccfadc075 DOM: Yet more new cards 2018-03-17 23:02:13 +00:00
Agetian
9bf4bacaee - Fixed Stangg. 2018-03-17 19:50:42 +03:00
Agetian
e23f807e63 - Clean up some code ugliness and fix a meaningless "unknown key: turn" error message in GameState. 2018-03-17 17:15:25 +03:00
Agetian
b959f61049 - Doomsday search effect should not be optional.
- Temporarily make it impossible to use the new mass selection code with cards that have the ChangeNum parameter on AF ChangeZone because it allows the human to select an arbitrarily small or big amount of cards without any limitation.
2018-03-17 16:32:50 +03:00
austinio7116
35245d5268 Cleaned up 3/4 player android code - ensured top to bottom turn order. 2018-03-16 08:24:51 +00:00
austinio7116
0781113fd9 Android 3/4 player working in landscape mode too 2018-03-15 20:36:04 +00:00
austinio7116
17e6c65652 Imrovements to layout of 3/4 player match view on android 2018-03-15 19:56:17 +00:00
austinio7116
2c15be8351 Alternative and better horizontal layout for 3/4 player games on Android 2018-03-15 12:00:48 +00:00
swordshine
fb3e5a68aa Merge branch 'patch' into 'master'
DOM: Added some more Dominaria cards

See merge request core-developers/forge!294
2018-03-15 06:22:33 +00:00
swordshine
ae17432ee3 Merge branch 'multi-select' into 'master'
allow human players to make mass select, sometimes

See merge request core-developers/forge!293
2018-03-15 06:21:43 +00:00
Jamin W. Collins
a564f49381 allow human players to make mass select, sometimes
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-14 20:32:25 -06:00
Misha Colbourne
e300bba4de Update rampaging_cyclops.txt 2018-03-15 02:29:35 +00:00
Misha Colbourne
23a5377e25 DOM: Added some more Dominaria cards 2018-03-15 02:19:55 +00:00
Misha Colbourne
ceaf19b9a1 DOM: Added some more Dominaria cards 2018-03-15 01:48:42 +00:00
Misha Colbourne
a144096ed3 DOM: Added some more Dominaria cards 2018-03-15 01:48:18 +00:00
swordshine
876083b085 Merge branch 'damageMap' into 'master'
Damage map

See merge request core-developers/forge!252
2018-03-15 01:00:20 +00:00
Misha Colbourne
7a4aa94b8b DOM: Added some more Dominaria cards 2018-03-15 00:14:19 +00:00
Misha Colbourne
f7e3497bd0 Merge branch 'patch' into 'master'
Patch

See merge request OgreBattlecruiser/forge!1
2018-03-15 00:08:51 +00:00
austinio7116
df61e5d9aa Update to android to support 3/4 player games 2018-03-14 23:05:07 +00:00
Misha Colbourne
d5872df7ed Fixed trigger description in vraskas_conquistador.txt 2018-03-14 22:48:28 +00:00
Misha Colbourne
dcecbd4e37 Update DeckFormat.java 2018-03-14 22:43:40 +00:00
Hanmac
9f7e02933f CostDamage: use spellAbility as cause 2018-03-14 07:15:04 +01:00
Hanmac
0b64ffb4c7 DamageMap: use cause for Lifelink part 2018-03-14 07:15:03 +01:00
Hanmac
51655cffe6 cards: remove PreventAllDamageBy 2018-03-14 07:15:03 +01:00
Hanmac
fffa4cf157 Card: move changed text and remove PreventAllDamageBy 2018-03-14 07:15:03 +01:00
Hanmac
c74b0a017b Damage: add Cause Param for damage deal, needed for Silhouette or Bronze Horse 2018-03-14 07:15:03 +01:00
Hanmac
70301e8e91 Blaze Commando: it is a DamageDoneOnce trigger 2018-03-14 07:15:03 +01:00
Hans Mackowiak
b88b0ec271 Master of Wild Hunt: add DamageMap 2018-03-14 07:15:03 +01:00
Hans Mackowiak
8ac13c0d6d RepeatEachEffect: reset damageMap after being used 2018-03-14 07:15:02 +01:00
Hanmac
79c43d7c9f cards: update cards with DamageMap 2018-03-14 07:15:02 +01:00
Hanmac
e9c02edfae DamageMap: use DamageMap for RepeatEach cards and similar 2018-03-14 07:15:02 +01:00
swordshine
47ea767348 Merge branch 'patch-2' into 'master'
Fix filename - lyra_dawnbriger.txt to lyra_dawnbringer.txt

See merge request core-developers/forge!291
2018-03-14 04:26:23 +00:00
Misha Colbourne
718762f0e0 Fix filename - lyra_dawnbriger.txt to lyra_dawnbringer.txt 2018-03-14 04:20:18 +00:00
swordshine
025da50f14 Merge branch 'patch' into 'master'
DOM: Added some new Dominaria cards

See merge request core-developers/forge!290
2018-03-14 04:17:50 +00:00
Misha Colbourne
d630e34249 Update lyra_dawnbriger.txt 2018-03-14 04:15:11 +00:00
Misha Colbourne
320b3cc885 Delete voltaic_servant.txt 2018-03-14 04:09:21 +00:00
Misha Colbourne
6d5d8efe1f Delete lyra_dawnbringer.txt 2018-03-14 04:08:13 +00:00
Misha Colbourne
e75bef451b Update territorial_allosaurus.txt 2018-03-14 03:34:51 +00:00
Misha Colbourne
12c84196f3 Update DeckFormat.java 2018-03-14 03:31:29 +00:00
Misha Colbourne
749b2ba7cb Update broken_bond.txt 2018-03-14 03:28:21 +00:00
Misha Colbourne
828e96fa90 Update baloth_gorger.txt 2018-03-14 03:24:50 +00:00
Misha Colbourne
c3643bb1e2 Update ancient_animus.txt 2018-03-14 03:21:21 +00:00
Misha Colbourne
59bb6d67a0 Update academy_journeymage.txt 2018-03-14 03:20:28 +00:00
Misha Colbourne
e11da6d01e DOM: Added some new Dominaria cards 2018-03-14 00:59:20 +00:00
Misha Colbourne
ea2764c5ba DOM: Added some new Dominaria cards 2018-03-14 00:35:30 +00:00
Misha Colbourne
120b3533bc DOM: Added some new Dominaria cards 2018-03-14 00:31:54 +00:00
swordshine
5e00a60eaa Merge branch 'patch' into 'master'
- Added a few new cards

See merge request core-developers/forge!289
2018-03-14 00:31:17 +00:00
Misha Colbourne
9d20ab4dad DOM: Added some new Dominaria cards 2018-03-14 00:17:12 +00:00
Misha Colbourne
b06b9ea379 DOM: Added some new Dominaria cards 2018-03-14 00:11:18 +00:00
Misha Colbourne
5bc415513b DOM: Added some new Dominaria cards 2018-03-13 23:50:04 +00:00
Misha Colbourne
9948fd4bf4 DOM: Added some new Dominaria cards 2018-03-13 23:29:04 +00:00
Misha Colbourne
ff66f3c8dd DOM: Added some new Dominaria cards 2018-03-13 23:15:22 +00:00
Misha Colbourne
f75f88435c DOM: Added some new Dominaria cards 2018-03-13 22:45:21 +00:00
Misha Colbourne
4efd0c96d1 DOM: Added some new Dominaria cards 2018-03-13 22:30:02 +00:00
Misha Colbourne
ca63bbc47c Update blink_of_an_eye.txt 2018-03-13 22:26:31 +00:00
Misha Colbourne
f20ed3468a DOM: Added some new Dominaria cards 2018-03-13 22:24:56 +00:00
Misha Colbourne
9de8501277 DOM: Added some new Dominaria cards 2018-03-13 21:55:16 +00:00
Misha Colbourne
1c68daeb39 Update blink of an eye.txt 2018-03-13 21:46:21 +00:00
Misha Colbourne
d0968696a6 DOM: Added some new Dominaria cards 2018-03-13 21:35:26 +00:00
Misha Colbourne
71278fe390 DOM: Added some new Dominaria cards 2018-03-13 19:04:58 +00:00
Misha Colbourne
71064e0f92 DOM: Added some new Dominaria cards 2018-03-13 18:38:04 +00:00
Misha Colbourne
3a93d723ea DOM: Added some new Dominaria cards 2018-03-13 18:13:38 +00:00
Misha Colbourne
88c12ba712 DOM: Added some new Dominaria cards 2018-03-13 17:52:47 +00:00
Misha Colbourne
cfb7fc237d DOM: Added some new Dominaria cards 2018-03-13 17:41:35 +00:00
Misha Colbourne
84a521869b DOM: Added some new Dominaria cards 2018-03-13 17:28:40 +00:00
Misha Colbourne
86adb641be DOM: Added some new Dominaria cards 2018-03-13 17:18:06 +00:00
Misha Colbourne
3d340e7b9f Update thallid_soothsayer.txt 2018-03-13 17:02:56 +00:00
Misha Colbourne
e2d7024974 Update voltaic_servant.txt 2018-03-13 16:49:12 +00:00
Misha Colbourne
635ce0fbda Adding some new Dominaria card scripts 2018-03-13 16:38:18 +00:00
Misha Colbourne
a438cc75b5 Adding some new Dominaria card scripts 2018-03-13 16:37:58 +00:00
Misha Colbourne
bf728971c0 Adding some new Dominaria card scripts 2018-03-13 16:01:17 +00:00
swordshine
2e45c1ace2 - Added 5 cards 2018-03-13 23:36:09 +08:00
swordshine
ebf9d99127 - DOM: Added 3 cards 2018-03-13 23:09:29 +08:00
swordshine
773c95af58 - DOM: Added six cards 2018-03-13 20:48:41 +08:00
swordshine
1990a54d5d - DOM: Added three cards 2018-03-13 20:02:49 +08:00
swordshine
b01f950a28 Merge branch 'master' into 'master'
- DOM: Added a few cards

See merge request core-developers/forge!288
2018-03-13 04:34:54 +00:00
swordshine
d66e041db3 - DOM: Added a few cards 2018-03-13 12:26:47 +08:00
swordshine
6b9cd02de8 Merge branch 'AIHermitDruid' into 'master'
Improves AI logic for Hermit Druid

See merge request core-developers/forge!240
2018-03-13 04:09:27 +00:00
Luke Way
572ecef291 Tabs to spaces 2018-03-12 21:06:05 -04:00
swordshine
4968d3efa6 Merge branch 'patch' into 'master'
Added a few Dominaria cards

See merge request core-developers/forge!283
2018-03-13 00:55:05 +00:00
swordshine
7d4f334ec7 Merge branch 'fabricate-index-out-of-bounds' into 'master'
Fabricate: if the only option is to create servo tokens, just do that

See merge request core-developers/forge!285
2018-03-13 00:53:59 +00:00
swordshine
4a11faf7d1 Merge branch 'ai-energy-check' into 'master'
Make AI check energy when testing if it can pay for a mana ability

See merge request core-developers/forge!284
2018-03-13 00:52:19 +00:00
swordshine
88f8840ef5 Merge branch 'deckgenimprovements' into 'master'
Deckgenimprovements

See merge request core-developers/forge!281
2018-03-13 00:51:21 +00:00
Luke Way
ce99b1093a -- Statically initialize value lookup map. This avoids problems when running simulations in multiple threads.
-- Replace Maps.newTreeMap with new HashMap<>().  newTreeMap is not necessary in Java 7+, and there's no need for a tree map here.
2018-03-12 13:38:46 -04:00
Luke Way
ab39d632fd Convert sysout to game log 2018-03-12 13:10:03 -04:00
Luke Way
7be473f99d Convert sysout to game log 2018-03-12 12:58:29 -04:00
Luke Way
2c5a845d1a Fabricate: if the only option is to create servo tokens, just do that. Avoids IndexOutOfBoundsException. 2018-03-12 12:56:17 -04:00
Luke Way
86e81d3584 Make AI check energy when testing if it can pay for a mana ability 2018-03-12 12:52:47 -04:00
swordshine
2120d4493e - DOM: Added Tiana, Ship's Caretaker 2018-03-12 09:51:14 +08:00
swordshine
57c9c2fe90 - Fixed Firesong and Sunspeaker: the trigger should check the root spellability
- FIXME: forge.game.card.CardDamageMap.triggerDamageDoneOnce(boolean) should add the source spellability to fix spells with Lifelink
2018-03-12 09:32:39 +08:00
swordshine
0251d5776e - DOM: Fixed Firesong and Sunspeaker so cycling Renewed Faith wouldn't trigger it 2018-03-12 09:10:18 +08:00
austinio7116
b0d5834396 Improved human/AI deck switching to remember and reload selected deck if still available 2018-03-11 20:22:36 +00:00
swordshine
7b837b4468 - DOM: Added a few cards 2018-03-11 23:23:29 +08:00
swordshine
67064b3275 - DOM: Added Oath of Teferi 2018-03-11 20:16:13 +08:00
swordshine
6f60bb264a - DOM: Added 3 cards 2018-03-11 19:28:34 +08:00
swordshine
b560df49a3 - DOM: Added Firesong and Sunspeaker 2018-03-11 18:23:47 +08:00
swordshine
d8b31ec4bc - DOM: Added a few cards 2018-03-11 17:09:04 +08:00
swordshine
90fc791aac Merge branch 'patch' into 'master'
Legendary Sorcery

See merge request core-developers/forge!282
2018-03-11 08:48:26 +00:00
maustin
64928efb54 Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements 2018-03-11 07:25:55 +00:00
austinio7116
9ca0d7244e Fixes to ensure deckchooser AI status is set correctly before decks are generated and that the list of card-based decks is updated to show only AI playable cards as required. 2018-03-11 07:25:43 +00:00
maustin
acb412b8f1 Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements
# Conflicts:
#	forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java
#	forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java
#	forge-gui/src/main/java/forge/interfaces/IUpdateable.java
2018-03-11 07:21:00 +00:00
austinio7116
4dd52e6f82 Fixes to ensure deckchooser AI status is set correctly before decks are generated and that the list of card-based decks is updated to show only AI playable cards as required. 2018-03-11 07:20:03 +00:00
swordshine
23fcaaf5d0 - DOM: Added Darigaaz Reincarnated 2018-03-11 13:19:46 +08:00
swordshine
cd9702fd03 - DOM: Added Karn, Scion of Urza, Jhoira's Familiar, Jodah, Archmage Eternal, Mishra's Self-Replicator, and Mox Amber 2018-03-11 11:59:27 +08:00
swordshine
082cfc164d - DOM: Added Primevals' Glorious Rebirth 2018-03-11 10:04:54 +08:00
swordshine
b94181c796 Merge branch 'patch' into 'master'
ExchangeLife Variant for Evra, Halcyon Witness, Tree of Redemption, and Tree of Perdition

See merge request core-developers/forge!280
2018-03-11 01:55:52 +00:00
austinio7116
13ad244be1 Fixes to ensure deckchooser AI status is set correctly before decks are generated and that the list of card-based decks is updated to show only AI playable cards as required. 2018-03-10 20:23:34 +00:00
maustin
ec1ad333cd Merge branch 'master' into deckgenimprovements 2018-03-10 20:21:50 +00:00
swordshine
ac75e554a9 - Added Karn's Temporal Sundering 2018-03-10 22:39:41 +08:00
swordshine
c249ed5f9f - DOM: Added "Legendary Sorcery" Urza's Ruinous Blast 2018-03-10 22:29:26 +08:00
swordshine
795c071e64 - DOM: Added Evra, Halcyon Witness 2018-03-10 18:05:40 +08:00
swordshine
fc130ebfb5 - DOM: Added Baird, Steward of Argive, Benalish Marshal, and Charge 2018-03-10 15:46:54 +08:00
swordshine
a0bf4f2da0 Merge branch 'patch' into 'master'
- DOM: added "Historic"

See merge request core-developers/forge!279
2018-03-10 07:24:00 +00:00
swordshine
2f57a39fcc - DOM: added "Historic" and Jhoira, Weatherlight Captain 2018-03-10 15:04:31 +08:00
Hans Mackowiak
211d6c793a Merge branch 'assorted-fixes' into 'master'
- A quick fix for ReplacementEffect hasRun on copied REs.

See merge request core-developers/forge!278
2018-03-08 22:35:40 +00:00
Agetian
79344ddad2 Merge remote-tracking branch 'origin/assorted-fixes' into assorted-fixes 2018-03-08 22:27:45 +03:00
Agetian
50a551f875 - A quick fix for ReplacementEffect hasRun on copied REs. 2018-03-08 22:21:12 +03:00
Agetian
884715054b - A quick fix for ReplacementEffect hasRun on copied REs. 2018-03-08 22:20:42 +03:00
Sol
9a3bc52ffb Merge branch 'network-play' into 'master'
Network Play

See merge request core-developers/forge!277
2018-03-08 02:15:10 +00:00
Jamin W. Collins
7615f333e1 disclose and enforce DEV_MODE state in network play
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-07 17:25:15 -07:00
Jamin W. Collins
d9913ffb1e broadcast message on player leaving, like joins
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-07 17:25:15 -07:00
Sol
534a161dc0 Merge branch 'fixes' into 'master'
Fixes

See merge request core-developers/forge!276
2018-03-07 21:46:27 +00:00
Hans Mackowiak
83b1965733 Merge branch 'master' into 'fixes' 2018-03-07 13:59:49 +00:00
Hanmac
d9df1243b6 mark effect as final 2018-03-07 07:04:57 +01:00
Hanmac
b88365e475 SpellAbility fixed some changes 2018-03-07 07:04:00 +01:00
Sol
090d4dc2c5 Merge branch 'assorted-fixes' into 'master'
Attempting to fix "Copy to Clipboard" not copying e.g. dual partner commanders.

See merge request core-developers/forge!274
2018-03-07 04:02:00 +00:00
Sol
97e17a7754 Merge branch 'a25draftingfixes' into 'master'
A25draftingfixes

See merge request core-developers/forge!275
2018-03-07 03:58:41 +00:00
Sol
a5db48c719 Merge branch 'deckgenimprovements' into 'master'
Card-based deck generation improvements and refactoring

See merge request core-developers/forge!260
2018-03-07 03:52:05 +00:00
Hanmac
65d65c9e5f Treasure map: update code using Branch to remove trigger 2018-03-06 22:18:23 +01:00
Hanmac
c5c3591471 TokenEffect: use sa instead of root 2018-03-06 22:11:35 +01:00
Hanmac
a47c3cf004 SetStateEffect: check for StoredTransform svar 2018-03-06 22:11:22 +01:00
Hanmac
f76c60e867 CommanderDeckGenerator: fixed getCommanderDecks 2018-03-06 22:10:20 +01:00
Hanmac
58bba637ca SimulateMatch: fixed simulateSingleMatch 2018-03-06 22:10:11 +01:00
Hanmac
59063bc194 CardTraitBase: use GameObject so it works with SpellAbility too 2018-03-06 22:08:27 +01:00
Hanmac
2e33b3162c AbilityUtils: fixed getSpellAbilities if sa is null 2018-03-06 22:07:53 +01:00
Hanmac
1e7755b877 CloneEffect: setOriginalHost instead of setHostCard 2018-03-06 22:06:44 +01:00
Hanmac
7b8e92ace2 CardFactoryUtil: use AdditionalAbilityList with better check for Fabricate 2018-03-06 22:06:24 +01:00
Hanmac
e25247d0e5 SpellAbility: add IsTargeting property, fixed additional abilities for clone 2018-03-06 22:03:00 +01:00
Hanmac
93dc3338c1 SpellAbility: isValid check for root 2018-03-06 22:01:51 +01:00
austinio7116
fcd392eec1 Added Masters 25 to draft blockdata for drafting and corrected Konming Sleeping Dragon quotes and rarity of Akroma 2018-03-06 19:50:36 +00:00
maustin
81b002ad63 Merge branch 'master' into deckgenimprovements 2018-03-06 19:47:06 +00:00
maustin
70feec2f5a Merge remote-tracking branch 'origin/master' 2018-03-06 19:46:33 +00:00
Agetian
4ddb73e8d2 - When copying deck to clipboard, do not attempt to check the validity of zone sizes to avoid situations when exceptional, but legal deck compositions (such as two partner Commanders) are not allowed / not fully copied. 2018-03-06 22:13:04 +03:00
austinio7116
ca099e077b Ensuring that non-AI cards are excluded from the list of card-based decks that can be selected for an AI player 2018-03-05 12:43:05 +00:00
austinio7116
30048e7a82 Moved repeated AI slot check logic into single boolean 2018-03-05 08:26:06 +00:00
Michael Kamensky
2ee05f46e7 Merge branch 'masters-25' into 'master'
Masters 25 booster box

See merge request core-developers/forge!272
2018-03-05 04:07:18 +00:00
Seravy
3ea30a4461 Basic lands from drafts, except foils, do not get added to the player's inventory, to avoid generating an infinite lands through "add basic land". This both removes potential abuse by selling them, and eliminates the annoyance of having these lands wasting space in your inventory. 2018-03-04 23:53:27 +01:00
Seravy
83bae5ad74 Sold basic lands that could have been added through "Add basic land" will not be removed from decks. 2018-03-04 23:36:48 +01:00
Rob Schnautz
4027e14266 Masters 25 2018-03-04 20:47:02 +00:00
maustin
a84da053a6 Updated card-based deck generation data 2018-03-04 13:58:53 +00:00
austinio7116
1b0955b959 Removed commented out code and moved comment to correct location 2018-03-04 06:35:39 +00:00
Michael Kamensky
bbcea47bfd Merge branch 'foil' into 'master'
move foil option to be available in all sections

See merge request core-developers/forge!269
2018-03-04 05:04:22 +00:00
Michael Kamensky
88f656f937 Merge branch 'master' into 'master'
- Preparing Forge for Android v1.6.7.001 [incremental].

See merge request core-developers/forge!270
2018-03-04 05:04:10 +00:00
Agetian
148e569182 - Preparing Forge for Android v1.6.7.001 [incremental]. 2018-03-04 08:01:25 +03:00
Jamin W. Collins
d8405e2fc8 move foil option to be available in all sections
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-03 12:08:41 -07:00
Sol
594ce263b6 Merge branch 'multiplayer' into 'master'
Multiplayer

Closes #161 and #151

See merge request core-developers/forge!268
2018-03-03 14:27:12 +00:00
austinio7116
73a623cc1b Fixed card-based deck generation where extra colours were kept despite no cards of that colour being included. 2018-03-03 10:53:57 +00:00
austinio7116
e0882ab6b5 Switched off logging in deckbuilder 2018-03-03 10:23:31 +00:00
maustin
c93d9d909d Merge branch 'master' into deckgenimprovements 2018-03-03 07:19:44 +00:00
maustin
8e5775dd2c Merge remote-tracking branch 'origin/master' 2018-03-03 07:16:41 +00:00
Michael Kamensky
dd38c0d581 Merge branch 'oracle-updates' into 'master'
Oracle updates

See merge request core-developers/forge!266
2018-03-03 04:28:57 +00:00
Jamin W. Collins
f01df7bdd2 display broadcast messages on server too
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-02 18:58:34 -07:00
Jamin W. Collins
ed16e6f4e9 option to fully reset the OnlineLobby
Closes core-developers/forge#161

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-02 18:58:34 -07:00
Jamin W. Collins
e9217f3261 fix server startup indentation for readability
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-02 18:58:34 -07:00
Jamin W. Collins
d2e4c53c20 Improve TargetSelection#chooseCardFromList
Closes core-developers/forge#151

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-02 18:58:34 -07:00
Jamin Collins
c402ae645e Merge branch 'masters25' into 'master'
Adding Masters25 cards and perliminary rankings

See merge request core-developers/forge!267
2018-03-03 00:25:54 +00:00
Chris H
9c69a33734 Adding Masters25 cards and perliminary rankings 2018-03-02 19:12:13 -05:00
Rob Schnautz
47eb18142d Update summoners_pact.txt 2018-03-02 22:25:11 +00:00
Rob Schnautz
0ca97727ad Update ash_barrens.txt 2018-03-02 22:24:14 +00:00
maustin
cd0cf2106a Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements 2018-03-02 20:09:24 +00:00
austinio7116
edc921bf98 Fixed bug with not counting keycards as lands in the land count causing various issues. Fixed final keyword issue causing build warnings. 2018-03-02 20:09:06 +00:00
austinio7116
b67c060211 Fixed bug with not counting keycards as lands in the land count causing various issues. Fixed final keyword issue causing build warnings. 2018-03-02 19:49:42 +00:00
maustin
cb5791276a Fixed AI commander and tiny leader decks not being passed correct forAI boolean so including non AI playable cards. Also allowed more lands in generated decks if required. 2018-03-02 19:28:05 +00:00
Michael Kamensky
d20317dd62 Merge branch 'patch-1' into 'master'
Oracle updates

See merge request core-developers/forge!261
2018-03-01 17:00:13 +00:00
austinio7116
9cfc42156d Remove dual lands from mono coloured generated decks 2018-03-01 16:07:09 +00:00
austinio7116
52d8e3d54f Fixed issue where generic mana cost creatures with colour identities were getting into decks that could not activate their abilities 2018-03-01 15:37:24 +00:00
maustin
186b56c335 Merge branch 'master' into deckgenimprovements 2018-03-01 12:41:28 +00:00
maustin
006bbf8fa0 Merge remote-tracking branch 'origin/master' 2018-03-01 12:41:11 +00:00
austinio7116
ec6ccb69dd Merge branch 'multiplayer' into 'master'
Multiplayer

See merge request core-developers/forge!265
2018-03-01 12:08:43 +00:00
Jamin W. Collins
81af09d86f ensure variants are only disabled for network play
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-01 04:41:30 -07:00
Jamin W. Collins
4299491104 avoid creating an additinal map of all cards
While getting all cards is needed for some network play variants, they
can be retrieved with the additional memory usage.  The nested lookup
isn't as elegant (IMO) but does avoid memory bloat with the additional
data structure.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-03-01 04:24:50 -07:00
austinio7116
ad693eb71f Switched off deck builder logging 2018-03-01 07:09:28 +00:00
maustin
02eb8f632f Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements
# Conflicts:
#	forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java
#	forge-gui/src/main/java/forge/limited/CardThemedDeckBuilder.java
2018-03-01 06:55:16 +00:00
austinio7116
352236c390 Moved magic number to static variable 2018-03-01 06:54:04 +00:00
austinio7116
cb5f5938f7 Fixed issue with using random n-color deck generators for commander and tiny leaders by switching to the same deck generator as the card-based deck generation for consistency and to prevent tiny leader decks being short of cards using the n-color generators. 2018-03-01 06:14:38 +00:00
austinio7116
ed4583ee7a Fixed adding wastes in colourless decks 2018-03-01 06:14:38 +00:00
austinio7116
d4b09f39ad Refactoring of card-based deck generation to share the same generation code so that the Modern/Standard deck generation gets the benefit of improvements made in the Commander deck generation code - also fixed a few bugs with land counts, generic mana cards with a colour identity. Reduced the minimum number of connections in the matrix required to include cards in the card-based deckgen list to get more cards included in the model. 2018-03-01 06:14:38 +00:00
austinio7116
25ab2ca2dc Refactoring of card-based deck generation to share the same generation code so that the Modern/Standard deck generation gets the benefit of improvements made in the Commander deck generation code - also fixed a few bugs with land counts, generic mana cards with a colour identity. Reduced the minimum number of connections in the matrix required to include cards in the card-based deckgen list to get more cards included in the model. 2018-03-01 06:13:25 +00:00
maustin
c6714fcc4e Merge remote-tracking branch 'origin/master' 2018-03-01 06:12:36 +00:00
Sol
0f558f0479 Merge branch 'fix-zealous-inquisitor' into 'master'
Fix Zealous Inquisitor spell description

See merge request core-developers/forge!264
2018-03-01 03:29:34 +00:00
Chris H
4ca87ab84e Fix Zealous Inquisitor spell description 2018-02-28 22:28:14 -05:00
Sol
90d9c45134 Merge branch 'multiplayer' into 'master'
Multiplayer

Closes #157

See merge request core-developers/forge!262
2018-03-01 03:12:26 +00:00
Jamin W. Collins
193f1f8b23 network play: enable Vanguard
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 20:03:53 -07:00
Jamin W. Collins
3415d76b40 allow ALL cards to be (de)serialized
This is needed by network play variants like:
- Vanguard
- Planechase

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 20:03:53 -07:00
Jamin W. Collins
a5b9512fdb network play: allow readying if Momir is selected
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 20:03:53 -07:00
Jamin Collins
8db64ffd0a Merge branch 'fix-npe-token-scripts' into 'master'
Fix NPE for Tokens created by Liliana the Last Hope

See merge request core-developers/forge!263
2018-03-01 02:15:12 +00:00
Chris H
9a6f407a6d Fix NPE for Tokens created by Liliana the Last Hope 2018-02-28 21:09:27 -05:00
Jamin W. Collins
26c0df6e05 network play: fix for Commander and Tiny Leaders variants
This method is called numerous times, many before the Commander data has
been populated on the PlayerView objects.  This is reflected a few lines
above where similar logic is applied to the current PlayerView.  This
simply applies similar logic to the player's opponents.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 18:24:19 -07:00
Jamin W. Collins
420d31f825 replicate PlayerView(s) from incoming to existing
With standard network play games, we got lucky.  With variants the
existing approach was not accurately reflecting state changes.  This
appears to be resolved by replicating incoming PlayerView objects onto
their existing versions inside the Tracker object.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 18:09:49 -07:00
Jamin W. Collins
72ca147b73 ensure that selected variant is applied to GameRules
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 17:59:38 -07:00
Jamin W. Collins
f1a001a834 retrieve GameType from the Lobby
The existing GameView does not accurately reflect the GameType when
variants are selected.  The Lobby does have the correct GameType.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 17:55:32 -07:00
Jamin W. Collins
2b704e16c7 disable all network play variants
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 17:48:45 -07:00
Jamin W. Collins
2c55a9d86c bump commons-lang3 version for Java 9 support
Closes core-developers/forge#157

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-28 17:44:30 -07:00
Rob Schnautz
e4d45a1369 Update eladamris_call.txt 2018-03-01 00:41:20 +00:00
Rob Schnautz
eebeb4a213 Update imperial_recruiter.txt 2018-03-01 00:40:46 +00:00
Rob Schnautz
c70791b25c Update ratcatcher.txt 2018-03-01 00:38:18 +00:00
austinio7116
9aa6990823 Fixed issue with using random n-color deck generators for commander and tiny leaders by switching to the same deck generator as the card-based deck generation for consistency and to prevent tiny leader decks being short of cards using the n-color generators. 2018-02-28 22:11:05 +00:00
maustin
67b04aa48b Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements 2018-02-28 18:07:46 +00:00
austinio7116
0dc62d67ef Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements 2018-02-28 18:07:37 +00:00
maustin
f3e91e22d9 Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements 2018-02-28 18:06:14 +00:00
austinio7116
f8f3f640e3 Fixed adding wastes in colourless decks 2018-02-28 18:05:58 +00:00
austinio7116
63a1fe0b9e Fixed adding wastes in colourless decks 2018-02-28 15:14:41 +00:00
maustin
d9dffe0151 Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements 2018-02-28 13:53:53 +00:00
austinio7116
6f28d0721e Refactoring of card-based deck generation to share the same generation code so that the Modern/Standard deck generation gets the benefit of improvements made in the Commander deck generation code - also fixed a few bugs with land counts, generic mana cards with a colour identity. Reduced the minimum number of connections in the matrix required to include cards in the card-based deckgen list to get more cards included in the model. 2018-02-28 13:53:43 +00:00
maustin
61eef2b86f Merge remote-tracking branch 'origin/deckgenimprovements' into deckgenimprovements
# Conflicts:
#	forge-gui/src/main/java/forge/limited/CardThemedDeckBuilder.java
2018-02-28 13:00:50 +00:00
austinio7116
74d0e4c4dc Refactoring of card-based deck generation to share the same generation code so that the Modern/Standard deck generation gets the benefit of improvements made in the Commander deck generation code - also fixed a few bugs with land counts, generic mana cards with a colour identity. Reduced the minimum number of connections in the matrix required to include cards in the card-based deckgen list to get more cards included in the model. 2018-02-28 13:00:10 +00:00
austinio7116
2b40b6e560 Refactoring of card-based deck generation to share the same generation code so that the Modern/Standard deck generation gets the benefit of improvements made in the Commander deck generation code - also fixed a few bugs with land counts, generic mana cards with a colour identity. Reduced the minimum number of connections in the matrix required to include cards in the card-based deckgen list to get more cards included in the model. 2018-02-28 12:10:53 +00:00
maustin
86878541f3 Merge remote-tracking branch 'origin/master' 2018-02-28 06:33:59 +00:00
Sol
b0a786970f Merge branch 'commanderDeckGenerator' into 'master'
Commander deck generator

See merge request core-developers/forge!249
2018-02-28 02:55:11 +00:00
maustin
92e548462d Merge remote-tracking branch 'origin/master' 2018-02-27 22:14:54 +00:00
austinio7116
d05e1e59dc Merge branch 'token_scripts' into 'master'
Allow scripts to be written for Tokens

See merge request core-developers/forge!236
2018-02-27 22:14:13 +00:00
austinio7116
9638153581 Attempting to revert accidental README change 2018-02-27 14:58:47 +00:00
maustin
0e06251424 Merge remote-tracking branch 'origin/commanderDeckGenerator' into commanderDeckGenerator 2018-02-27 14:52:56 +00:00
austinio7116
c5c04e762f Code reformat to improve readability and supportiblity whilst addressing issues raised in friarsol's code review 2018-02-27 14:52:41 +00:00
austinio7116
e361f09691 Code reformat to improve readability and supportiblity whilst addressing issues raised in friarsol's code review 2018-02-27 08:17:48 +00:00
austinio7116
1b1eb6636d Fixed mobile bug where card-gen decks disabled but still tries to load standard card-based decks 2018-02-27 06:40:59 +00:00
austinio7116
80a2d902c0 Updated card-based deck generation matrix with latest decks 2018-02-27 00:22:35 +00:00
austinio7116
6940eec453 Merge remote-tracking branch 'origin/commanderDeckGenerator' into commanderDeckGenerator 2018-02-26 23:58:22 +00:00
austinio7116
aaf27f2d60 Added options to disable card based deck generation and to ensure it fails gracefully if .dat data and/or decks folder is missing 2018-02-26 23:54:27 +00:00
austinio7116
9cb1f2787c Assigning lstDecks.getGameType().getDeckFormat() to a variable so we don't have to write it out each conditional 2018-02-26 23:49:47 +00:00
austinio7116
c047ec8588 Update to card view of decks so long-press opens deck viewer to be consistent with list view 2018-02-26 23:49:47 +00:00
austinio7116
2b54468923 Increased basic land count in random commander decks.
Fixed lobby update on variant change
2018-02-26 23:49:47 +00:00
austinio7116
e8f257d2cf Completed random commander deck generation refactoring to work on Desktop, to support partner commanders and corrected a number of bugs found during testing. Full support on android and desktop for saving of selected deck states for the new features added. 2018-02-26 23:49:47 +00:00
austinio7116
66763e537d Addition of new new random commander deck generation - both fully random decks based on a selected commander and matrix-based synergistic deck generation using a model learned from thousands of commander decks. This also includes support for commander net-decks on desktop. 2018-02-26 23:49:47 +00:00
austinio7116
0eee296664 Added model file for new commander deck generation and updated modern and standard models too 2018-02-26 23:49:47 +00:00
austinio7116
2891133c5c Update to card view of decks so long-press opens deck viewer to be consistent with list view 2018-02-26 23:49:47 +00:00
austinio7116
6231775997 Increased basic land count in random commander decks.
Fixed lobby update on variant change
2018-02-26 23:49:47 +00:00
austinio7116
61ff4d50fd Completed random commander deck generation refactoring to work on Desktop, to support partner commanders and corrected a number of bugs found during testing. Full support on android and desktop for saving of selected deck states for the new features added. 2018-02-26 23:49:47 +00:00
austinio7116
13560e3b53 Error handling and thread timeout added to simulated matches to allow large AI tournaments without infinite length games due to loops. AI tournaments should now always finish - if the matches take too long they are scored as a draw. 2018-02-26 23:49:47 +00:00
austinio7116
52ae852953 Addition of new new random commander deck generation - both fully random decks based on a selected commander and matrix-based synergistic deck generation using a model learned from thousands of commander decks. This also includes support for commander net-decks on desktop. 2018-02-26 23:49:47 +00:00
austinio7116
7761706fb5 Added model file for new commander deck generation and updated modern and standard models too 2018-02-26 23:49:47 +00:00
maustin
d25437c8e0 Merge remote-tracking branch 'origin/master' 2018-02-26 23:45:17 +00:00
Hans Mackowiak
6883ef1e9f Merge branch 'cardstatefix' into 'master'
CardState: fixed copy with keywordcache

See merge request core-developers/forge!259
2018-02-26 06:22:35 +00:00
Hanmac
6bb9dd1028 CardState: fixed copy with keywordcache 2018-02-26 07:20:51 +01:00
Blacksmith
d6278541dd Clear out release files in preparation for next release 2018-02-25 22:55:10 +00:00
Blacksmith
5977598c71 [maven-release-plugin] prepare for next development iteration 2018-02-25 22:49:44 +00:00
Blacksmith
35706d71f7 [maven-release-plugin] prepare release forge-1.6.7 2018-02-25 22:49:41 +00:00
Blacksmith
c38f0900d1 Update README.txt for release 2018-02-25 22:48:23 +00:00
Blacksmith
23ed82c0ba Update README.txt for release 2018-02-25 21:56:40 +00:00
Blacksmith
ea4b82f11e Fix SCM URLs again 2018-02-25 21:56:15 +00:00
Blacksmith
8cfff4299a Fix SCM URLs 2018-02-25 21:35:49 +00:00
tehdiplomat
56250bea63 Added token scripts to be used with Wurmcoil Engine and Tolsimir Wolfblood
The actual changes to use them currently not committed.
2018-02-25 15:36:16 -05:00
Sol
d4d376c8c6 Merge branch 'PlaneshiftPromos' into 'master'
Fixing Planeshift set alter foils in boosters

See merge request core-developers/forge!202
2018-02-25 20:19:19 +00:00
Blacksmith
0146357eed Remove test file 2018-02-25 18:13:04 +00:00
Blacksmith
9759264aec Test direct commit access for Blacksmith 2018-02-25 18:06:22 +00:00
Sol
c17465a2a7 Merge branch 'git-pom-update' into 'master'
Update pom file to support git releases

See merge request core-developers/forge!258
2018-02-25 17:52:34 +00:00
KrazyTheFox
dc3795d1cd Update pom file to support git releases 2018-02-25 12:45:02 -05:00
Sol
fdf2b26fdf Merge branch 'multiplayer' into 'master'
Multiplayer

See merge request core-developers/forge!257
2018-02-25 04:45:31 +00:00
Jamin W. Collins
eff0012ea1 add warning to server connect dialog
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-24 15:32:30 -07:00
Jamin W. Collins
d74186e93c update release notes
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-24 14:12:29 -07:00
Jamin Collins
fe108bb354 Merge branch 'multiplayerandroidfixes' into 'master'
Fix to get multiplayer working on android - added more classes to the "keep"…

See merge request core-developers/forge!255
2018-02-24 21:03:49 +00:00
Jamin W. Collins
9fb556f180 add Serialization to Cost objects
Resolves forge/#160

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-24 14:02:09 -07:00
Michael Kamensky
f1db204e0d Merge branch 'challengerdecks3' into 'master'
URGENT - Added metadata to new challenger decks to fix NPE

See merge request core-developers/forge!256
2018-02-24 18:39:54 +00:00
Jamin W. Collins
fd35c8d862 add option to be alerted on receipt of priority
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-24 10:51:43 -07:00
maustin
4d134e5087 Merge remote-tracking branch 'origin/challengerdecks3' into challengerdecks3
# Conflicts:
#	forge-gui/res/quest/precons/Hazoret Aggro.dck
#	forge-gui/res/quest/precons/Second Sun Control.dck
#	forge-gui/res/quest/precons/Vehicle Rush.dck
2018-02-24 17:25:19 +00:00
austinio7116
1e3b4e34e5 Added metadata to new challenger decks to fix NPE 2018-02-24 17:24:53 +00:00
austinio7116
95a02bece7 Added metadata to new challenger decks to fix NPE 2018-02-24 17:22:32 +00:00
austinio7116
62ffb786b2 Fix to get multiplayer working on android - added more classes to the "keep" list in proguard.cfg. Lots more testing to do, but can start a game Android vs pc 2018-02-24 15:58:31 +00:00
Michael Kamensky
4d41ed60c9 Merge branch 'challengerdecks2' into 'master'
Added first batch of preconstructed challenger decks

See merge request core-developers/forge!253
2018-02-24 06:48:26 +00:00
Sol
fc8624c93b Merge branch 'multiplayer' into 'master'
ensure that View(s) are used for ManaAbility choice(s)

See merge request core-developers/forge!251
2018-02-24 00:14:05 +00:00
Sol
800a77b2e0 Merge branch 'multiplayerchatfixes' into 'master'
Extended multiplayer chat text box to 255 character max from 60 and fixed multiplayer chat focus stealing issue

See merge request core-developers/forge!254
2018-02-23 23:58:33 +00:00
austinio7116
361443d845 Extended multiplayer chat text box to 255 character max from 60
Prevented the game from stealing focus from the multi player chat box to ensure you do not auto end turn or pass priority due to typing in chat when you regain priority
2018-02-23 21:20:25 +00:00
maustin
7db9a6730a Merge remote-tracking branch 'origin/challengerdecks2' into challengerdecks2
# Conflicts:
#	forge-gui/res/quest/precons/Counter Surge.dck
#	forge-gui/res/quest/precons/Vehicle Rush.dck
2018-02-23 19:38:10 +00:00
austinio7116
5a784e6c9b Added first batch of preconstructed challenger decks 2018-02-23 19:37:31 +00:00
austinio7116
91c16a319c Added first batch of preconstructed challenger decks 2018-02-23 19:33:20 +00:00
maustin
47ebfec59e Merge remote-tracking branch 'origin/commanderDeckGenerator' into commanderDeckGenerator
# Conflicts:
#	forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java
2018-02-23 18:54:26 +00:00
austinio7116
f582469ba2 Assigning lstDecks.getGameType().getDeckFormat() to a variable so we don't have to write it out each conditional 2018-02-23 18:43:15 +00:00
Jamin W. Collins
06bc5e074a ensure that View(s) are used for ManaAbility choice(s)
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-22 20:06:23 -07:00
austinio7116
d4c3dc4eab Update to card view of decks so long-press opens deck viewer to be consistent with list view 2018-02-22 20:13:02 +00:00
austinio7116
cfeb07c65f Increased basic land count in random commander decks.
Fixed lobby update on variant change
2018-02-22 20:13:02 +00:00
austinio7116
ced56ec4cd Completed random commander deck generation refactoring to work on Desktop, to support partner commanders and corrected a number of bugs found during testing. Full support on android and desktop for saving of selected deck states for the new features added. 2018-02-22 20:13:02 +00:00
austinio7116
3cf215dbd0 Error handling and thread timeout added to simulated matches to allow large AI tournaments without infinite length games due to loops. AI tournaments should now always finish - if the matches take too long they are scored as a draw. 2018-02-22 20:13:02 +00:00
austinio7116
f237a8d29a Addition of new new random commander deck generation - both fully random decks based on a selected commander and matrix-based synergistic deck generation using a model learned from thousands of commander decks. This also includes support for commander net-decks on desktop. 2018-02-22 20:13:02 +00:00
austinio7116
4d5a1a152f Added model file for new commander deck generation and updated modern and standard models too 2018-02-22 20:13:02 +00:00
austinio7116
6735bf3e76 Update to card view of decks so long-press opens deck viewer to be consistent with list view 2018-02-22 08:51:11 +00:00
Michael Kamensky
85adc466b3 Merge branch 'master' into 'master'
- Code base cleanup.

See merge request core-developers/forge!237
2018-02-22 04:16:51 +00:00
Sol
afc02a856d Merge branch 'multiplayer' into 'master'
further multiplayer fixes

See merge request core-developers/forge!250
2018-02-22 01:29:23 +00:00
Jamin W. Collins
21d6b02410 submit previous deck if sideboarding timeout occurs
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-21 17:27:03 -07:00
Jamin W. Collins
5b0b719ce9 use SpellAbilityView for generic effect choice(s)
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-21 17:13:33 -07:00
Jamin W. Collins
9ce0315c3e give remote players 5 minutes per question
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-21 17:10:51 -07:00
austinio7116
6fe34ec618 Increased basic land count in random commander decks.
Fixed lobby update on variant change
2018-02-21 18:56:22 +00:00
maustin
05a30d0129 Merge remote-tracking branch 'origin/commanderDeckGenerator' into commanderDeckGenerator
# Conflicts:
#	forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java
#	forge-gui-mobile/src/forge/deck/FDeckChooser.java
#	forge-gui/res/deckgendecks/Commander.dat
#	forge-gui/res/deckgendecks/Modern.dat
#	forge-gui/res/deckgendecks/Standard.dat
#	forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java
#	forge-gui/src/main/java/forge/deck/DeckType.java
#	forge-gui/src/main/java/forge/deck/DeckgenUtil.java
#	forge-gui/src/main/java/forge/limited/CardThemedCommanderDeckBuilder.java
2018-02-21 09:30:49 +00:00
austinio7116
6cf2c3459c Completed random commander deck generation refactoring to work on Desktop, to support partner commanders and corrected a number of bugs found during testing. Full support on android and desktop for saving of selected deck states for the new features added. 2018-02-21 09:28:17 +00:00
austinio7116
e87513bb45 Error handling and thread timeout added to simulated matches to allow large AI tournaments without infinite length games due to loops. AI tournaments should now always finish - if the matches take too long they are scored as a draw. 2018-02-21 09:12:48 +00:00
austinio7116
0f5c1b8e8e Addition of new new random commander deck generation - both fully random decks based on a selected commander and matrix-based synergistic deck generation using a model learned from thousands of commander decks. This also includes support for commander net-decks on desktop. 2018-02-21 09:12:48 +00:00
austinio7116
c109ac76a7 Added model file for new commander deck generation and updated modern and standard models too 2018-02-21 09:12:48 +00:00
Sol
d7bca1c232 Merge branch 'multiplayer' into 'master'
multiplayer fixes for graveyard and stack targeting

See merge request core-developers/forge!248
2018-02-21 04:11:16 +00:00
Jamin W. Collins
0a034f9acb ensure Stack targeting uses views
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-20 20:47:21 -07:00
Jamin W. Collins
98774c408c ensure that TargetSelection presents CardView(s)
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-20 20:20:41 -07:00
Jamin W. Collins
802036f693 print a message when unable to send event
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-20 15:27:18 -07:00
Agetian
5bb9ac6fcd - More cleanup. 2018-02-20 08:02:31 +03:00
Agetian
fcc02ed600 Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2018-02-20 07:55:13 +03:00
Sol
1e45d14ec3 Merge branch 'AftermathFix' into 'master'
CardFactory: fixed keywords for SplitCards

See merge request core-developers/forge!239
2018-02-20 02:42:39 +00:00
Sol
97027e657e Merge branch 'multiplayer' into 'master'
further multiplayer fixes

See merge request core-developers/forge!238
2018-02-20 02:41:48 +00:00
Jamin W. Collins
487fec0258 multiplayer fix for 2nd and 3rd games of match
The server was not updating the lobby player's game reference.  So, the
remote client's response was being processed for the wrong game
instance.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-19 16:03:48 -07:00
Hanmac
b25bc72f3e CardFactory: fixed keywords for SplitCards 2018-02-19 21:37:16 +01:00
Jamin W. Collins
d086e4d692 cleanup remote client game state creation
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-19 13:23:07 -07:00
Jamin W. Collins
8d2d66abb9 move external address out to a class attribute
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-19 13:23:07 -07:00
tehdiplomat
6ca1269261 Add ability to load tokens via scripts before 2018-02-19 13:15:12 -05:00
Chris H
d374da0481 Refactor TokenEffect to be easier to add in TokenScripts 2018-02-19 13:15:12 -05:00
Agetian
83242e1fa9 - More cleanup. 2018-02-19 20:56:05 +03:00
Agetian
35fa4e7d8f - More cleanup. 2018-02-19 20:50:45 +03:00
Agetian
5022e96a48 - More cleanup. 2018-02-19 20:50:09 +03:00
Agetian
1779c8f84e - More cleanup and Price of Progress logic fix. 2018-02-19 20:15:12 +03:00
Michael Kamensky
74d9791f03 Merge branch 'ScorchedEarth' into 'master'
Scorched earth

See merge request core-developers/forge!234
2018-02-19 16:52:41 +00:00
Michael Kamensky
a6cb96c758 Merge branch 'BattleCry' into 'master'
Gerrard's Battle Cry : play before combat

See merge request core-developers/forge!235
2018-02-19 16:51:59 +00:00
Agetian
b05463ad6c - Code base cleanup. 2018-02-19 19:48:06 +03:00
Seravy
f229fbc4b1 Update DestroyAi.java 2018-02-19 12:54:16 +00:00
Seravy
8d4f72c691 Protect only if we are actually needing to protect the card the ability will work on. 2018-02-19 13:14:16 +01:00
Seravy
1d470f87d4 Gerrard's Battle Cry : play before combat 2018-02-19 12:55:59 +01:00
Seravy
04260a7f45 Ai can now activate protection abilities that do not target but affect only a single card, such as Knights of Dawn 2018-02-19 11:42:02 +01:00
Seravy
3c03199ea3 Do not play protection if enchanted by aura of the color that would be chosen 2018-02-19 11:30:58 +01:00
Seravy
acd68f86d1 Add Svar to all cards where applicable 2018-02-19 11:22:52 +01:00
Seravy
23e312f2ba Now it works 2018-02-19 11:18:20 +01:00
Seravy
3931903018 New SVar "ChosenProtection" Marks Auras that would grant protection from the chosen color, to prevent attaching to a creature that already would have protection from that color. While picking a different color would be another solution, there is a chance it's wasteful to protect from a secondary color, or the player plays only one color so it's better to not target that creature at all.
This attempt didn't work, next commit did.
2018-02-19 11:16:10 +01:00
Michael Kamensky
8c680da98c Merge branch 'PriceOfProgress' into 'master'
Price of progress

See merge request core-developers/forge!210
2018-02-19 04:37:54 +00:00
Michael Kamensky
21e682f54d Merge branch 'deck-editor' into 'master'
improve how foiling is done

See merge request core-developers/forge!233
2018-02-19 04:27:28 +00:00
Seravy
70593c2b6a Try to play Scorching Earth before playing a land for higher X. 2018-02-18 23:35:44 +01:00
Seravy
6b64667767 Do not spend more on X than the number of lands we can discard! 2018-02-18 21:37:20 +01:00
Hanmac
96d1777132 ChangeZoneEffect: it should Fizzle when removing to Command zone too 2018-02-18 20:58:12 +01:00
Jamin W. Collins
f6bd725633 improve how foiling is done
Previous implementation relied on the removed card becoming focused in
the full card inventory.  This implementation works directly with the
deck avoiding the full inventory.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-18 12:51:55 -07:00
Michael Kamensky
dd7e2d933f Merge branch 'DumpTurn' into 'master'
Dump/Setup game state will now save current game turn.

See merge request core-developers/forge!209
2018-02-18 19:39:22 +00:00
Seravy
97177e379e Default turn = 1 if none set 2018-02-18 20:22:17 +01:00
Seravy
8df00a18e8 This should be 0 2018-02-18 19:55:59 +01:00
Michael Kamensky
6668fa5505 Merge branch 'revert-a9e15c2d' into 'master'
Revert "Merge branch 'Guavafix' into 'master'"

See merge request core-developers/forge!232
2018-02-18 18:54:35 +00:00
Seravy
c348ffa34e Now using SVar 2018-02-18 19:54:29 +01:00
Michael Kamensky
275e1be6be Revert "Merge branch 'Guavafix' into 'master'"
This reverts merge request !217
2018-02-18 18:54:20 +00:00
Seravy
c18924a350 Removed the newline. :( 2018-02-18 19:41:26 +01:00
austinio7116
8325893369 Error handling and thread timeout added to simulated matches to allow large AI tournaments without infinite length games due to loops. AI tournaments should now always finish - if the matches take too long they are scored as a draw. 2018-02-18 18:07:57 +00:00
austinio7116
66fd90332d Addition of new new random commander deck generation - both fully random decks based on a selected commander and matrix-based synergistic deck generation using a model learned from thousands of commander decks. This also includes support for commander net-decks on desktop. 2018-02-18 18:05:08 +00:00
austinio7116
4dad9e3478 Added model file for new commander deck generation and updated modern and standard models too 2018-02-18 18:01:09 +00:00
Hanmac
0c005bb3ff Commander: CommanderTax now is on the Player who casted the Commander 2018-02-18 18:47:59 +01:00
Hanmac
57b9a1b4c7 SpellAbility: copy manaPart 2018-02-18 18:07:41 +01:00
Hanmac
b29b1b3630 CardState: fixed copyState and LKI 2018-02-18 18:06:38 +01:00
Hanmac
3a071ea071 DestroyAi: extended havepact logic 2018-02-18 11:26:34 +01:00
Hanmac
25efe15329 fix final in ACEditor 2018-02-18 11:17:48 +01:00
Michael Kamensky
d544d69524 Merge branch 'AIInDangerRange' into 'master'
AI In danger threshold now configurable and can be random instead of being locked to always 4.

See merge request core-developers/forge!220
2018-02-18 09:15:34 +00:00
Michael Kamensky
bcc0fc9e00 Merge branch 'AINoncombatantCreatures' into 'master'
Ai noncombatant creatures

See merge request core-developers/forge!221
2018-02-18 09:10:39 +00:00
Seravy
7f2d2d6588 AI In danger threshold now configurable and can be random instead of being locked to always 4.
(As a player, I find the fact the AI always changes their behavior towards more defensive at exactly 5 life extremely abusable - I can always be 100% sure they won't block certain creatures or activate certain spells/abilties otherwise, and in many cases, if the AI already got down to 5 life, even if they do activate their big trump card, it's too late : the last 5 damage is way easy to deal.)

Used this for playing the past few days and it seemed to work without problems.
2018-02-18 10:08:29 +01:00
Michael Kamensky
2e70eb6718 Merge branch 'master' into 'master'
- Minor cleanup.

See merge request core-developers/forge!230
2018-02-18 08:59:38 +00:00
Agetian
b7601fded3 - Minor cleanup. 2018-02-18 11:59:10 +03:00
Michael Kamensky
13ace49230 Merge branch 'AISpiritMirror' into 'master'
Ai spirit mirror

See merge request core-developers/forge!223
2018-02-18 08:57:23 +00:00
Seravy
229ecc9ba7 missing an "s" in script 2018-02-18 09:39:18 +01:00
Seravy
602f761552 This should work? 2018-02-18 09:39:17 +01:00
Seravy
f271c7a74e Now the AI properly realizes this can be played, but doesn't actually play it.
Is there something that prevents it from playing things that destroy own tokens?
2018-02-18 09:39:15 +01:00
Seravy
c47242e40e fixed card name 2018-02-18 09:39:14 +01:00
Seravy
887f2f9a52 Mark Spirit Mirror and Breeding Pit tokens as things to sacrifice.
If Grave Pact is in play, activate Spirit Mirror to destroy own token and force enemy to sac a creature if possible.
2018-02-18 09:39:13 +01:00
Michael Kamensky
6b528a6d99 Merge branch 'DeckResetFixing' into 'master'
Deck reset fixing

See merge request core-developers/forge!213
2018-02-18 08:36:08 +00:00
Seravy
7ff8564077 Merge branch 'master' of https://git.cardforge.org/core-developers/forge 2018-02-18 09:35:39 +01:00
Michael Kamensky
77e79653ba Merge branch 'master' into 'master'
- Minor cleanup and updating CHANGES.txt.

See merge request core-developers/forge!229
2018-02-18 08:31:14 +00:00
Agetian
4665503caa - Minor cleanup and updating CHANGES.txt. 2018-02-18 11:29:02 +03:00
Michael Kamensky
1f922362a1 Merge branch 'mobile-dev' into 'master'
add commandline parsing/options to mobile-dev

See merge request core-developers/forge!216
2018-02-18 08:25:21 +00:00
Michael Kamensky
f5e095f345 Merge branch 'cantblock' into 'master'
CantBlockBy as Static Ability

See merge request core-developers/forge!218
2018-02-18 08:05:59 +00:00
Michael Kamensky
a392fc43f9 Merge branch 'deck-viewer' into 'master'
improve "Copy to Clipboard" - merge reprinted cards

See merge request core-developers/forge!215
2018-02-18 07:41:49 +00:00
Michael Kamensky
d444e8896c Merge branch 'Fireslinger' into 'master'
Fireslinger

See merge request core-developers/forge!211
2018-02-18 07:36:27 +00:00
Michael Kamensky
935d42af74 Merge branch 'SuddenImpact' into 'master'
Force use of Sudden Impact during enemy draw step after drawing if enemy holds…

See merge request core-developers/forge!212
2018-02-18 07:34:03 +00:00
Michael Kamensky
a9e15c2d33 Merge branch 'Guavafix' into 'master'
The guava fix

See merge request core-developers/forge!217
2018-02-18 07:17:15 +00:00
Michael Kamensky
b5ae52a3b6 Merge branch 'AiNonstacking' into 'master'
AI Nonstacking Permanents

See merge request core-developers/forge!222
2018-02-18 05:54:50 +00:00
Michael Kamensky
abf405fd20 Merge branch 'deck-editor' into 'master'
add option to foil cards in a deck

See merge request core-developers/forge!219
2018-02-18 05:50:49 +00:00
Michael Kamensky
3215eb9a8a Merge branch 'CounterFromAbilityAI' into 'master'
Counter from ability ai

See merge request core-developers/forge!224
2018-02-18 05:49:09 +00:00
Michael Kamensky
f2ddd697e9 Merge branch 'AICuriosity' into 'master'
Ai curiosity

See merge request core-developers/forge!225
2018-02-18 05:47:57 +00:00
Michael Kamensky
9e6fc59505 Merge branch 'Oaths' into 'master'
Oaths

See merge request core-developers/forge!227
2018-02-18 05:45:47 +00:00
Michael Kamensky
2dc0e10c34 Merge branch 'TreasureTroveFix' into 'master'
AI Logic name in code and actual card scripts did not match. Assuming the…

See merge request core-developers/forge!228
2018-02-18 05:45:14 +00:00
Sol
2a086c8b64 Merge branch 'token-artwork' into 'master'
Token artwork

See merge request core-developers/forge!214
2018-02-18 03:08:38 +00:00
Seravy
cf9e3a5f1d "Tap to deal X damage to target player" should be a priority target even if it's a defender or 0 attack creature 2018-02-17 22:58:45 +01:00
Seravy
91616396b7 Also no need to block if Awakening is in play. Adding here to prevent future merge conflict with Fog having the same effect. 2018-02-17 21:21:06 +01:00
Seravy
300e3fd4d3 Fixed using the fog effect on "about to die" Spike Weaver 2018-02-17 21:14:59 +01:00
Jamin W. Collins
373e11bc41 add option to foil cards in a deck
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-17 12:42:56 -07:00
Seravy
4cb140512a AI and autopayments now work correctly with Reflecting Pool.
When the player has to pick colors, it'll always use the menu for consistency, instead of using the side panel for choices between 2 colors and the menu for 3+.
2018-02-17 19:53:23 +01:00
Seravy
bb27eab819 Merge branch 'Guavafix' 2018-02-17 16:53:49 +01:00
Jamin W. Collins
d99dd5f19f add commandline parsing/options to mobile-dev
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-17 08:50:41 -07:00
Jamin W. Collins
d60d350fcb token-artwork - try removing variant as a fallback
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-17 08:48:46 -07:00
Jamin W. Collins
b1e7e407a7 token artwork - try uppercase set name
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-17 08:48:46 -07:00
Jamin W. Collins
4e7d338144 improve "Copy to Clipboard" - merge reprinted cards
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-17 08:48:45 -07:00
Hanmac
4528541b3f AiController: add orderSa for token and pump effects for Hero of Bladehold 2018-02-17 16:25:41 +01:00
Hanmac
a829dc72bc CardFactoryUtil: make CantBeBlockedBy into new StaticAbility 2018-02-17 15:25:51 +01:00
Seravy
9e58ec0f80 React to removal of Spike Weaver by using the effect if it's already planning to do later
Don't leave blockers if planning to use fog effect, except the card that will do so. (might need to tap to use it, or might get killed if attacking)
2018-02-17 14:04:31 +01:00
Seravy
606f52e493 Improved feature 2018-02-17 13:18:24 +01:00
Seravy
769f6ea976 Reserve mana for fog effects if in danger 2018-02-17 12:38:52 +01:00
Seravy
b6395aa463 The guava fix 2018-02-17 12:06:57 +01:00
Hanmac
3f1d6238ff CardFactoryUtil: add Battle cry as trigger 2018-02-17 12:01:53 +01:00
Seravy
89eed17839 Reserve mana for fog effects 2018-02-17 12:01:39 +01:00
Hanmac
e990c3648e cards: update card scripts for new CantBlockByStatic 2018-02-17 10:29:07 +01:00
Seravy
67f9be5d4d endswith->equals 2018-02-17 01:19:37 +01:00
Seravy
8650a147aa AI Logic name in code and actual card scripts did not match. Assuming the scripts (6 cards) are correct as no card uses the other name. 2018-02-17 00:46:32 +01:00
Hanmac
8490f8d90b Card: fixed CARDNAME & EFFECTSOURCE on the static abilties 2018-02-16 20:55:17 +01:00
Hanmac
8f1c7bf40a EffectEffect: add AtEOT, remembering host card 2018-02-16 20:44:48 +01:00
Hanmac
ec87b64d0c Blazing Torch: CantBlockBy example 2018-02-16 16:51:37 +01:00
Hanmac
27eee4c8fa Card: show CantBlockBy on affected creature 2018-02-16 16:50:43 +01:00
Hanmac
af32152bcc CantBlockBy: now done as static ability 2018-02-16 16:49:25 +01:00
Hanmac
e72edb95b5 Manifest: fixed check with worms of the earth 2018-02-16 14:02:34 +01:00
Hanmac
efed143a11 ReplaceMoved: fixed zonetype check 2018-02-16 14:01:07 +01:00
Seravy
99f64d7565 Add fear to evasion abilities 2018-02-16 12:34:35 +01:00
Seravy
32ef472b0c AI prefers evasion or "tap to deal damage to player" creatures to enchant with these. 2018-02-16 12:22:50 +01:00
Sol
7ade1b9a52 Merge branch 'multiplayer' into 'master'
Multiplayer

See merge request core-developers/forge!207
2018-02-16 01:07:35 +00:00
Sol
cc664d1280 Merge branch 'DefaultFiltersOption' into 'master'
Default filters option

See merge request core-developers/forge!175
2018-02-16 01:00:20 +00:00
Jamin W. Collins
84874601a9 Continue but log NullPointerException in getMana
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-15 17:08:01 -07:00
Jamin W. Collins
b47d528bf0 ensure that remote client has trackers defined
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-15 17:07:14 -07:00
Seravy
59612c45ea Resolving conflicts 2018-02-15 10:03:54 +01:00
Seravy
e8be6f674e Add quest mode option to make Foil and Personal Rating filters default on all editor screens. 2018-02-15 10:01:59 +01:00
Seravy
33554dd9cc changed order 2018-02-15 09:48:26 +01:00
Seravy
4dd73930e3 p->P 2018-02-15 09:40:28 +01:00
Seravy
af14c33e39 Fixed problems with previous commit 2018-02-14 22:26:54 +01:00
Seravy
7e9312a93c Added :
-Never play if would suicide, unless we are losing the game next turn anyway AND it results in a double KO
-If AI has the cards that work better on small hand, do play Price of Progress even if it doesn't deal good damage to the enemy. (important synergy within Tempest block with major tournament tier cards)
2018-02-14 21:39:49 +01:00
Seravy
4048ba18f3 Merge remote-tracking branch 'remotes/origin/AINecrologia' into PriceOfProgress 2018-02-14 21:34:24 +01:00
Seravy
3ed563d3f1 Force use of Sudden Impact during enemy draw step after drawing if enemy holds 4+ cards - there likely won't be a better chance, the AI does need enough untapped mana and the human player will likely play cards over time, emptying their hand.
TODO : Ideally, the AI should hold 4 mana untapped as soon as it has 4 lands in play, and play Sudden Impact ASAP if there are 4+ cards, before the human has a chance to use up any.

Note : This might be slightly suboptimal for decks where the AI is forcing the other player to draw, and thus might deal more damage if waiting, but considering cards that force draw are usually played before this due to their mana cost and timing, I don't think this will be a problem (if such AI decks even exist right now) - especially as using one now and another later is always better than only using one and those decks should have many copies.
2018-02-14 20:58:51 +01:00
Seravy
11aef9f054 Never use if would kill self by doing so 2018-02-14 19:52:20 +01:00
Seravy
556a8e7176 One more branch where it could pick the player so "avoidtgtP" needs to be added 2018-02-14 19:36:57 +01:00
Seravy
465066c04c Had to move this part to a new "AvoidTargetP" function - shouldtargetP is not absolute, the AI still targets the player if there is no better target move, but we want it to not use the ability instead. 2018-02-14 19:20:57 +01:00
Seravy
668eaa1a7c Orcish Artielly marked as self damage
Will now target players if granted Lifelink or equivalent even if otherwise would not - self damage is not a concern
Are there any other cards that need AILogic "SelfDamage" or Svar "LikeLifeLink" added? If you are aware of any, please add!
2018-02-14 18:20:04 +01:00
Seravy
0bed673340 Do not use Fireslinger on players if our life lower
Do prefer targeting the player if has a "when this deals damage to an opponent" trigger

Important : The AI should be able to recognize the ability stacks - if there are multiple cards that deal damage, they should be used to kill the target together. (for example, tap two prodigal sorcerers to kill a 2/2 creature!)
Unfortunately, I don't think I can implement this with my current knowledge.
2018-02-14 18:06:13 +01:00
Seravy
13f606999c Improved playing conditions 2018-02-14 16:32:50 +01:00
Seravy
b0677ca006 Merge branch 'DumpTurn' into PriceOfProgress 2018-02-14 16:14:22 +01:00
Seravy
22846db1a3 Dump/Setup game state will now save current game turn.
Needed to test cards and AI that care about how many turns it have been since the start of the game.
2018-02-14 16:13:44 +01:00
Seravy
c9b634a274 fixed order of operations 2018-02-14 14:09:18 +01:00
Seravy
c37a492cb7 Was in wrong file, moved 2018-02-14 13:27:17 +01:00
Seravy
92f663c753 AI for Price of Progress 2018-02-14 13:25:13 +01:00
Seravy
35a2f9bf2b Fixes Ghouls - this one benefits the player with the MORE cards, not less. 2018-02-14 12:46:09 +01:00
Michael Kamensky
2dcf4b23b7 Merge branch 'AIDestroyAllInDanger' into 'master'
AI will activate ~DestroyAll~ abilities during combat if it would lose the game,…

See merge request core-developers/forge!206
2018-02-14 03:48:09 +00:00
Michael Kamensky
22b2f30ffb Merge branch 'AICoffinQueen' into 'master'
Will not untap Coffin Queen if already reanimating a creature

See merge request core-developers/forge!204
2018-02-14 03:46:50 +00:00
Michael Kamensky
9a7215a2a2 Merge branch 'AINecrologia' into 'master'
Ai necrologia

See merge request core-developers/forge!205
2018-02-14 03:44:30 +00:00
Michael Kamensky
6d4cc6efcb Merge branch 'bloodyCardTraits' into 'master'
CardTaits: make cardtraits inside LKI has the same id's as the in the original

See merge request core-developers/forge!197
2018-02-14 03:28:25 +00:00
Jamin W. Collins
45a0c54fb8 Define getter for TrackableObject props
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-13 18:44:28 -07:00
Jamin W. Collins
e8c9c8b90c Define getter for ClientGameLobby
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-13 18:41:56 -07:00
Jamin W. Collins
367fcad53c Define getter for GameView
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-13 18:38:58 -07:00
Jamin W. Collins
3a6c5b0cd5 Allow arbitrarily setting a Tracker
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-13 18:36:19 -07:00
Jamin W. Collins
375adab087 gracefully handle failure to connect to server
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-13 18:33:25 -07:00
Jamin W. Collins
fbf0f98c3a Limit team editing to each player
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-13 18:30:58 -07:00
Jamin W. Collins
711342829b Improve interruption messaging
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-13 18:28:47 -07:00
Jamin W. Collins
daf3be6346 Improve reported multiplayer host IP
The existing IP selection logic was less than optimal on Linux hosts
where it would frequently select and report 127.0.0.1 as the IP address
to share.  The new logic will create a datagram socket, use it to locate
an interface with a default route, and further backtrack to the
interface's IPv4 address.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-13 18:16:10 -07:00
Seravy
06edf9351a Oath of Druids, Oath of Lieges, Oath of Ghouls
Do not play if the enemy would immediately benefit from them in their upkeep.
2018-02-14 01:06:46 +01:00
Seravy
ead64864f4 Now it actually works and doesn't break the card.
Marked card AI playable.

Note : This also adds a new function to ComputerUtil,that checks if the AI is in danger of being killed, or if it has a same amount of life remaning - useful for future "pay life" AI decisions.
2018-02-13 18:07:51 +01:00
Seravy
8521387312 AI can play Necrologia 2018-02-13 17:40:03 +01:00
Seravy
3e5b47185e Special case for Null Brooch because it needs to be used even though its cost has a "discard". 2018-02-13 14:59:15 +01:00
Seravy
b5dbb7da90 Countering from abilities will have a 100% chance to get used, unless it costs cards to activate, otherwise normal counter settings are used.
This ensures the AI never wastes an opportunity to counter a spell if it does not cost him a card - holding a "tap to counter target spell" ability for later use is generally bad as the AI cannot determine if they need the mana later or not - but in most cases they don't.

Required for cards Null Brooch and Ertai Wizard Adept in Tempest quest world.
2018-02-13 13:57:30 +01:00
Seravy
03886905bd Will not untap Coffin Queen if already reanimating a creature 2018-02-13 13:03:44 +01:00
Seravy
2b688a99ff Improves AI logic for Hermit Druid
-Will now use at end of enemy turn at 100% chance instead of wasting opportunity to get land and fill grave

Not sure if "RemRandomDeck" is still needed? Card is now safe to play unless the deck has fewer than 4-5 basic lands in it.
2018-02-13 11:19:55 +01:00
Sol
f01282f12f Merge branch 'patch-6' into 'master'
Several updates to formats.txt

See merge request core-developers/forge!203
2018-02-13 03:06:11 +00:00
Sol
5e5fca9722 Merge branch 'LifeTotalBonusOption' into 'master'
Life total bonus option

See merge request core-developers/forge!176
2018-02-13 02:57:25 +00:00
Seravy
2ff32d3b90 MIssing ! in the check for nonvigilence creature. 2018-02-13 03:11:18 +01:00
Seravy
3b72bd18f0 Now quest data needs to be saved when changing decks, instead of quest preferences. 2018-02-13 02:14:42 +01:00
Rob Schnautz
fcb7f89001 typo (Trace Secrets > Trade Secrets) 2018-02-13 00:49:25 +00:00
Rob Schnautz
38cebf52c3 missed a couple 2018-02-13 00:48:05 +00:00
Seravy
56983b2553 Moves "Current_Deck" from Quest Preferences to Quest Data.
Without this, the selected deck name was global for all quests - switching to another quest retained the deck name from the previous quest
and if the new quest had a deck with the identical name, selected that one, otherwise selected nothing. (in one weird case, this resulted in quest data that crashed caused bugs when loading forge - as quest was saved with a selected deck from another quest data.)
Preferred behavior is to select the deck that was last selected in THAT QUEST when switching to another quest.
2018-02-13 01:37:00 +01:00
Rob Schnautz
04b347b968 we're not ready for block formats 2018-02-13 00:21:21 +00:00
Rob Schnautz
08a87ea112 Add conspiracy and ante cards to ban lists, update Standard, Modern, and Commander. Modern list is officially effective the 19th, all the rest is current. Add block formats. Reorder formats in the same order shown on the website. Move Extended to the end. To do: remove Extended. 2018-02-12 23:56:45 +00:00
Seravy
67e8046af6 Remove the line that resets the selected deck index to 0, so the next time the deck tab is opened, it autopicks the first deck instead of keeping what was last chosen. 2018-02-13 00:43:26 +01:00
Sol
dc11fad342 Merge branch 'QuestDraftRotation' into 'master'
Quest draft rotation

See merge request core-developers/forge!179
2018-02-12 03:06:40 +00:00
Seravy
8f9857e72d Fixing Planeshift set alter foils in boosters
-Now always foil, and with the (guesstimated) correct chance.
2018-02-12 01:11:03 +01:00
Seravy
76bcbc632e Rearranged to fix rounding problem 2018-02-12 00:03:09 +01:00
Seravy
e04d88977e adding to cards L-Z
Added creatures with low power that have
-abilities that tap for something really valuable like drawing a card
-abilities that usually need to be saved until enemy turn (counter a spell, tap a creature, prevent damage)
-abilities that usually deal equal or more damage if not attacking (prodigal sorcerer, grim lavamancer)
Generally avoided abilities where activating before combat is an acceptable choice and thus the attack decision doesn't come up anyway as the AI already used the ability.
However, this behavior is unacceptable on certain abilities (mostly those with higher mana costs) which should not be used before the end of main 2 as it prevents the AI from actually playing their cards. (like Hanna Ship Navigator)

I suspect this will need quite a bit of improvement in the future but we have to start somewhere.
2018-02-11 22:13:58 +01:00
Hanmac
3e613a8b80 cards: remove triggerZones from Unequip trigger, it doesn't work with ChangeZoneAll effects 2018-02-11 21:04:49 +01:00
Seravy
1f364168e8 adding to cards D-K 2018-02-11 20:55:44 +01:00
Seravy
07439cfeeb Exclude creatures with vigilance - attacking does not prevent using the ability 2018-02-11 20:27:03 +01:00
Seravy
94eda92d21 Adding to cards A-C 2018-02-11 20:15:51 +01:00
Seravy
15715cec10 Tested and now it works, unfortunately it's not as smart as I originally wanted, but it'll do. 2018-02-11 18:34:33 +01:00
Seravy
3662bb8e84 Implements Feature 2018-02-11 17:31:20 +01:00
Sol
14fc75be44 Merge branch 'QuestDraftSetFix' into 'master'
Quest draft set fix

See merge request core-developers/forge!174
2018-02-11 15:18:12 +00:00
Sol
a489e0845b Merge branch 'BonusForWinsOption' into 'master'
Bonus for wins option

See merge request core-developers/forge!173
2018-02-11 15:16:10 +00:00
Michael Kamensky
acf7a23965 Merge branch 'HornedKavuFix' into 'master'
Fixes Horned Kavu being played incorrectly (returning another Horned Kavu or itself)

See merge request core-developers/forge!201
2018-02-11 14:26:20 +00:00
Hanmac
a96a2fd825 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-02-11 14:53:58 +01:00
Hanmac
d455653f11 Debuff: with keyword changes, directly remove of keywords is not needed anymore, let changed keywords handle that 2018-02-11 14:53:27 +01:00
Seravy
8579ae99a0 Fixes Horned Kavu being played incorrectly (returning another Horned Kavu or itself) 2018-02-11 14:42:41 +01:00
Hanmac
da5cb3d905 check lands: use shorter code for etb replacement 2018-02-11 12:11:04 +01:00
Michael Kamensky
81315e3db4 Merge branch 'assorted-fixes' into 'master'
- Simplify a target num check in AiController.

See merge request core-developers/forge!200
2018-02-11 10:28:08 +00:00
Agetian
8a880a3f89 - Simplify a target num check in AiController. 2018-02-11 13:27:11 +03:00
Michael Kamensky
d5da9a80f6 Merge branch 'assorted-fixes' into 'master'
- Fixed the AI targeting restrictions check, added an AI logic for The Scarab God.

See merge request core-developers/forge!199
2018-02-11 05:09:39 +00:00
Agetian
670966be57 - Fixed the AI targeting restrictions check in AiController to account for the AILogics that can pre-target cards.
- Added an AI logic for The Scarab God (can be generalized later).
2018-02-11 07:46:38 +03:00
Michael Kamensky
5879690fc0 Merge branch 'upcoming_rix2' into 'master'
Refresh of Frontier Cube to include RIX

See merge request core-developers/forge!194
2018-02-11 03:54:52 +00:00
Seravy
ff054d44b7 Adding to cards where needed part 3 - Done up to base set 10th ED, expansions Apocalypse 2018-02-11 02:14:58 +01:00
Seravy
e909cfc744 Adding to cards where needed part 2 2018-02-11 01:49:43 +01:00
Seravy
b51ca84a8f Adding to cards where needed (and removing from cards where it isn't safe to rely on one copy, like Moat) 2018-02-11 00:28:22 +01:00
Seravy
3a9af0480d Improves "NonStackingEffect" Svar behavior for the AI.
The AI will now not play a second copy of these cards if and only if, there is no copy of the card owned by that AI in play, except copies that are enchanted by Auras owned by other players.
Later, a better logic that actually determines whether the enchanting aura removes the relevant ability from the card or not should be added - Warp Artifact will not stop a disk from activating, but Arrest will (if it's a creature).

Note : Only add NonStackingEffect to permanents the AI shouldn't play more than one of at a time if and only if, removal of enchantment is not pontentially leathal to the AI.
For example, do not add to Ensnaring Bridge or Moat, as loss of those allows the enemy to swing with possibly accumulated large armies for likely too much damage.
Those cards will need a different logic, that avoids playing a second copy if and only if the threat the card protects from is already too dangerous.
2018-02-10 23:21:03 +01:00
Michael Kamensky
ebd0a8096c Merge branch 'error-msg-fixes' into 'master'
Disambiguate class name in error StorageReader errors

See merge request core-developers/forge!196
2018-02-10 18:54:01 +00:00
Michael Kamensky
a1f2f8d944 Merge branch 'svn-to-git' into 'master'
make development version reference GIT not SVN

See merge request core-developers/forge!198
2018-02-10 18:53:39 +00:00
Jamin W. Collins
0a4dc6961d make development version reference GIT not SVN
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-10 11:13:41 -07:00
Hanmac
2e196fc41b CardTaits: make cardtraits inside LKI has the same id's as the in the original object\nReplaceMoved now works with Blood Moon and Blood Sun 2018-02-10 18:50:22 +01:00
Jamin W. Collins
844a7b5d76 Disambiguate class name in error StorageReader errors
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-02-10 10:28:39 -07:00
Michael Kamensky
ce343ffcdb Merge branch 'mobile-release' into 'master'
- Preparing Forge for Android publish v1.6.6.004 [incremental/bugfixes]

See merge request core-developers/forge!195
2018-02-10 16:17:12 +00:00
Agetian
25972aaa29 - Preparing Forge for Android publish v1.6.6.004 [incremental/bugfixes] 2018-02-10 19:11:33 +03:00
Seravy
0fe8c1f121 AI will activate ~DestroyAll~ abilities during combat if it would lose the game, or it would take too much damage (Nevinnyrral's Disk, Pernicious Deed) 2018-02-10 13:30:22 +01:00
austinio7116
cb37cf0889 Refresh of Frontier Cube to include RIX 2018-02-10 09:36:49 +00:00
Michael Kamensky
b5721a7e36 Merge branch 'OkkFix' into 'master'
Had wrong card name in Oracle text

See merge request core-developers/forge!192
2018-02-10 03:35:54 +00:00
Michael Kamensky
93b40ad867 Merge branch 'descriptions' into 'master'
Descriptions

See merge request core-developers/forge!193
2018-02-10 03:35:40 +00:00
Rob Schnautz
a3e5e40385 description, premium Spearbreaker Behemoth 2018-02-10 01:17:19 +00:00
Rob Schnautz
e13ed6f46e description, premium Paleoloth 2018-02-10 01:15:18 +00:00
Rob Schnautz
65dffbbe3a description 2018-02-10 01:12:15 +00:00
Rob Schnautz
16dafc9c57 description
(cherry picked from commit 909ee41e066207a9c3e4d943fd190e229765be66)
2018-02-10 01:06:35 +00:00
Rob Schnautz
bcd2f95e4d description
(cherry picked from commit f274dbe74e3fcda7e97c40df996b8b1533c04673)
2018-02-10 01:06:16 +00:00
Rob Schnautz
51ae7fee7e description 2018-02-10 00:55:19 +00:00
Rob Schnautz
2eb276f995 description, premium Treasury Thrull 2018-02-10 00:51:03 +00:00
Rob Schnautz
6a98f9947a description 2018-02-10 00:48:28 +00:00
Rob Schnautz
66cbd1a30e description 2018-02-10 00:45:17 +00:00
Rob Schnautz
c8d1b56d9e description, premium Phyrexian Hydra 2018-02-10 00:44:44 +00:00
Rob Schnautz
457ce2958e description, premium Doomwake Giant 2018-02-10 00:44:01 +00:00
Rob Schnautz
b7b6d5672e description, premium Conundrum Sphinx 2018-02-10 00:15:14 +00:00
Rob Schnautz
7dac6197e3 premium Djinn of Wishes 2018-02-10 00:12:03 +00:00
Rob Schnautz
ea40b439ee description, premium Resolute Archangel 2018-02-10 00:05:32 +00:00
Rob Schnautz
7061f7f26a premium Flameblast Dragon 2018-02-10 00:00:30 +00:00
Rob Schnautz
de1342a5d7 description, premium Jace's Mindseeker 2018-02-09 23:57:44 +00:00
Rob Schnautz
2f976dbeb3 description, premium Hellkite Charger 2018-02-09 23:51:56 +00:00
Seravy
e37e242bae Had wrong card name in Oracle text 2018-02-10 00:13:48 +01:00
Michael Kamensky
78c676e858 Merge branch 'assorted-fixes' into 'master'
- Updating CHANGES.txt.

See merge request core-developers/forge!191
2018-02-09 18:31:48 +00:00
Agetian
9e74ef9e37 - Updating CHANGES.txt. 2018-02-09 21:30:37 +03:00
Michael Kamensky
1740be22ae Merge branch 'InvasionWorld' into 'master'
Invasion world

See merge request core-developers/forge!189
2018-02-09 18:23:10 +00:00
Hanmac
35c499a074 Crafty Cutpurse: make it use a Control Layer for the Token Create Effect 2018-02-09 17:07:48 +01:00
Seravy
048093522a Revert "Fixes compile/run bugs in IntelliJ"
This reverts commit 36917c0d18

(cherry picked from commit 1cf40f45ad9f39f98dd3232d0ae01e9c0c31329d)
2018-02-09 10:34:39 +00:00
Seravy
66c696bfec rotam ->rotation amount. 2018-02-09 11:26:04 +01:00
Seravy
5dc5082c4e Added MAX to option variable name. 2018-02-09 10:58:07 +01:00
Seravy
42f79630e6 Deleted commented parts as suggested 2018-02-09 10:43:43 +01:00
Seravy
f8ccf0a91e Adding suggested simplification 2018-02-09 10:25:35 +01:00
Seravy
b924c1a80c W -> w 2018-02-09 10:04:59 +01:00
Michael Kamensky
42a6340a95 Merge branch 'assorted-fixes' into 'master'
- Updating CHANGES.txt.

See merge request core-developers/forge!188
2018-02-09 05:20:46 +00:00
Agetian
8b2b1d453d - Updating CHANGES.txt. 2018-02-09 08:19:35 +03:00
Michael Kamensky
e70207fbac Merge branch 'assorted-fixes' into 'master'
- Updating CHANGES.txt.

See merge request core-developers/forge!186
2018-02-09 05:19:02 +00:00
Agetian
846db416ec - Updating CHANGES.txt. 2018-02-09 08:18:33 +03:00
Michael Kamensky
84288aafe9 Merge branch 'patch-5' into 'master'
Duel Decks: Elves vs. Inventors

See merge request core-developers/forge!185
2018-02-09 04:48:36 +00:00
Rob Schnautz
d2d4318659 Add new file 2018-02-09 04:17:32 +00:00
Michael Kamensky
324c3000a0 Merge branch 'assorted-fixes' into 'master'
- File name fix, updating CHANGES.txt.

See merge request core-developers/forge!184
2018-02-09 03:48:11 +00:00
Agetian
fc6d1d2ab8 - Filename fix.
- Updating CHANGES.txt.
2018-02-09 06:47:28 +03:00
Michael Kamensky
7fb12fa5b3 Merge branch 'mtgo-editions' into 'master'
Five editions from MTGO

See merge request core-developers/forge!182
2018-02-09 03:37:07 +00:00
Michael Kamensky
791e53a935 Merge branch 'amonkhet' into 'master'
Amonkhet quest world

See merge request core-developers/forge!181
2018-02-09 03:36:24 +00:00
Sol
3a705c3e5f Merge branch 'patch-5' into 'master'
comment out boosters until later to prevent corruption of quest files when shop…

See merge request core-developers/forge!183
2018-02-09 01:58:20 +00:00
Rob Schnautz
ca61291995 comment out boosters until later to prevent corruption of quest files when shop tries to generate a booster 2018-02-09 01:45:57 +00:00
Rob Schnautz
a875557000 Commander Theme Decks 2018-02-09 01:38:33 +00:00
Rob Schnautz
517590883c Duel Decks: Mirrodin Pure vs. New Phyrexia 2018-02-09 01:19:35 +00:00
Rob Schnautz
ffa58f71f0 Magic Online Deck Series (TD0, but since this abbreviation was used for two sets, using the TD1 abbreviation that MTGO skipped) 2018-02-09 01:08:08 +00:00
Rob Schnautz
9017f1488f Treasure Chests 2018-02-09 00:59:39 +00:00
Rob Schnautz
c6bac3a247 Legendary Cube 2018-02-08 23:46:55 +00:00
Seravy
a7e48e8af0 The Domain deck is currently AI unplayable, but likely easy to fix later, see (20)
Most decks were tested in the old version (roughly 2017 October-November?).
They are AI playable albeit the AI doesn't play them perfectly - still it's good enough to include the world in a release. AI can always be improved later, as long as it doesn't self-defeat all the time with all the deck. (which was a real issue in the Tempest block where ~20 out of the 40 decks were unplayable for the AI, thus that world is still unfinished - AI has to be improved first.)
This is a list of issues I observed with the AI while testing the Invasion decks :

AI minor :
2. AI should prioritize returning Flametongue Kavu or Kavu Climer or other beneficial trigger creatures for gating creatures (or to any other bonuce effects)
3. Pernicious Deed should have that flag that prevents the AI from playing a second copy (same for Obvlivion Stone, Nevynnirral's Disk if they don't have it yet)
4. The AI seems to prefer playing normal lands first, "enters play tapped" lands later. That's usually the wrong play for the first few turns, where the AI wouldn't have a card to play from the mana anyway.
5. The AI seems to randomly send Captain Sisay to attack or search a card, this should be done smarter, always search if hand is not full and enemy isn't below 7 life?
6. Would be nice if the AI could recognize Rith's ability as one that produces potential blockers for the next turn.
9. When the AI plays a second copy of Teferi's Moat, it shouldn't pick the same color (and shouldn't play the second copy if the opponent plays only one color)
11. Not related to the invasion block but a generic issue : The AI prefers to kill the creatures I steal with Preacher instead of Preacher itself. I'm winning 99% of the games where I draw that card due to this, which likely messes up the difficulty testing results.
13. AI probably shouldn't play Skizzik without kicker as it sometimes does so then doesn't attack anyway or should learn to always attack with it if it wasn't kicked.
15. The AI had enough mana to play Urza's Rage with kicked and could have won the game by doing so, yet it played it without kicker and dealt only 3 damage. This happened more than once, it probably can't pay the kicker cost at all.
17. The AI uses Order // Chaos to kill creatures that are already having leathal damage on the stack from Powerstone Minefield
18. The AI sometimes doesn't seem to be able to use Tahngarth's ability, other times uses it during upkeep. It should never be used before the declare attackers step. (take advantage of vigilance)
19. Once the AI attacked with a 2/2 Quiron Dryan into my 2/3. It had Wax // Wane in hand but no mana to play it - all 3 lands were tapped.
21. Making Pulse of Llanowar, Elfhame Sanctuary and Fertile Ground AI playable would likely increase the AI playability of the block a lot (currently none of these cards are included in AI decks)
23. The AI used a counterspell to counter a spell targeting Glimmering Angel instead of activating the ability
25. The AI activated Crosis the Purger when I had zero cards in my hand.
26. Creatures with the "tap target creature" ability like Benalish Trapper and Stormscape Appreneic should not attack if the opponent has creatures that could be tapped next upkeep to prevent them from attacking.
(this seems to be a generic problem - small creatures with important noncombat abilties should be marked "stay home" and not attack unless the enemy is so low on health they can be defeated by 3 swings from the creature. This was one of the main problems
in Tempest block, for example Hermit Druid, Tradewind Rider and Ertai Wizard Adept. I really wanted to implement AI logic for this myself but...)
27. Yawgomoth's Ageanda should also be marked as a spell where the AI should never play a second copy (also it shouldn't be played without a large graveyard but I don't think the AI did that even once so maybe that's already implemented?)
33. The AI always picks "lose 1 life" on Putrid Warrior even if the opposite would be better.
36. AI played played a creature before Desolation Giant in the same turn.
39. AI doesn't seem to pay a kicker cost for Verdeloth the Ancient even if it has mana to do so.
40. AI uses Nightscape Master to bounce cards it could be killing with the other ability instead.
41. AI probably shouldn't use Recoil to bonuce own cards.

AI major :
1. AI should not play gating creatures if it would return another gating creature to hand, unless it's triggering kavu lair or sparkcaster ability.
7. The AI shouldn't sacrifice Fires of Yawimaya to deal 2 more damage when I still have 20 life. This should only be used to save creatures, or deal leathal damage to a blocker or player.
7b - it especially shouldn't do this is that results in the buffed creature no longer having haste and failing to attack!
8. The AI uses Rout as an instant during its own upkeep instead of waiting until main 1 and saving 2 mana (or possibly attacking first and doing it in main 2), this should never be used as an instant during the AI's own turn.
10. The AI seems to consistently fail paying both kicker costs on volvers even if it can afford doing it - it only pays one of them.
12. AI might be unable to pay the kicker for Dralnu's Pet - it had the mana and creature in hand but didn't do it. (and the creature was of a color the AI cannot play from hand)
14. Like Rout, Ghitu Fire also shouldn't be played as an instant during the AI's own upkeep. In fact, the AI generally should not use instant effects or cards during its own upkeep, it's very rarely the correct move, if ever.
22. The AI seems to be unable to play the card "Meddling Mage" even though it's marked with a "?" not an "AI" tag. I wouldn't expect it to play "fair" and pretend it has no idea what cards are in my deck (it guesses the color of my cards for other choices anyway) but in worst case it should at least play it as a 2/2 for 2 and name any random card it has no copies of in its deck.
24. The AI does not consider the board when picking colors for Voice of All. Even if I have two big white flying creatures in play, it still picks black because my deck has more black cards than white.
28. Once the AI had Temporal Spring and AEther Mutation in hand, and 4 life while I had 3 2/x creatures and it didn't play either bonuce spell. Maybe it was holding mana to cast Fact or Fiction later? (it had 5 lands, 3 forest 2 island)
(surprisingly it didn't even try to play Fact or Fiction before I won. Bug?)
29. The AI probably shouldn't play Phyrexian Arena when at low (below 10?) health.
30. AI didn't activate Pernicious Deed to destroy my 3/3 animated swamp that was killing them, even though it only takes an activation cost of 0. It also didn't activate it when I had a 4/4, they had a 4/4 and I had a 2/1 flying and they had 2 life.
After further games : In general the AI does not use Pernicious Deed very well (on top of playing 2-3 copies), and lets me hit them each turn with creatures and even dies, as long as I don't play too many at a time.
While I won't rate decks unplayable for just this, I'm pretty sure the decks including the card perform significantly worse than they could be and the card is borderline unplayable in the sense the AI obstinently refuses to activate it to save itself.
I suspect there is a threashold for how much advantage it expects from the card, and will not use it otherwise, even if not using it means losing the game. (even if I would be the only one losing permanents!)
31. The AI doesn't attack with units enchanted by Armadillo Cloak, and keeps them as blockers, even if it could gain several times the life it would lose to not blocking by the attack (AI had a 7/7 trample with it, I had two 2/x to attack and a 2/1 flying)
32. On the long term some sort of logic to decide whether to waste a kicker card immediately or wait for mana to use it with kicker would be nice, but it'll likely be hard to make one. (This is especially important for creatures, like Kavu Titan, volvers, etc)
34. When the AI has a 1/1 creature enchanter with Quicksilver Dagger, it still uses it to attack instead of tapping it to deal damage AND draw a card. (and this actually risks losing the creature to a "destroy target attacking..." spell)
35. The AI should play Warp Devotion before the bounce spells. As is, that deck works poorly.
37. AI had a Bloodfire Colossus in play, had active red mana, I had 3 life, it had 12, didn't activate to win, instead used Rout to destroy all creatures, and it didn't even sac it in response!
(I thought we improved the DamageAll logic? Is this card not using it?)
38. AI doesn't recognize Gerrard Capashen's ability to tap when attacking, so it fails to notice it could win the game as I only have 3 life and 1 blocker which could be tapped.

AI severe :
16. The AI seems to be unable to play Orim's Thunder at all, even if I have both a creature and an artifact and the creature is weak enough to be killed by the kicker effect. It can't even play it without kicker.
(I'll rank the deck with this card 1 higher to account for this bug getting fixed in the future - most of the AI's losses came from holding these and not using them to kill my creatures.)
20. The AI is unable to play Harrow and Allied Strategies even if it has no other cards to play and has mana. I unfortunately have to conclude the "Domain" deck is not AI playable as is - land searching and drawing are essential for it.
Note : I did see the AI play Harrow once in a blue moon, for example when it was about to die in combat, but it usually does not play the card.
28. AI used Dromar to return itself to hand by picking white (I had no white creatures). As all 5 dragon legends are actual opponents with their cards in the deck, and Dromar's doesn't have another creature, this is quite a big problem.
2018-02-09 00:27:00 +01:00
Rob Schnautz
41b73da329 fix set 2018-02-08 23:08:28 +00:00
Rob Schnautz
48d4016f8d fix set 2018-02-08 23:05:45 +00:00
Rob Schnautz
ed3624ce7b Upload New File 2018-02-08 22:51:53 +00:00
Rob Schnautz
58eebd8bfd Upload New File 2018-02-08 22:51:43 +00:00
Rob Schnautz
cc95200289 Upload New File 2018-02-08 22:51:33 +00:00
Rob Schnautz
7035993a3f Upload New File 2018-02-08 22:51:22 +00:00
Rob Schnautz
663b10f767 Upload New File 2018-02-08 22:51:11 +00:00
Rob Schnautz
922e45a80d Upload New File 2018-02-08 22:51:01 +00:00
Rob Schnautz
3dca3752bc Upload New File 2018-02-08 22:50:51 +00:00
Rob Schnautz
336ca104e9 Upload New File 2018-02-08 22:50:41 +00:00
Rob Schnautz
7492e7d450 Upload New File 2018-02-08 22:50:29 +00:00
Rob Schnautz
7440702fea Upload New File 2018-02-08 22:50:14 +00:00
Rob Schnautz
e568f84dcb Upload New File 2018-02-08 22:48:10 +00:00
Rob Schnautz
d043161bdd Upload New File 2018-02-08 22:47:57 +00:00
Rob Schnautz
9768b80736 Upload New File 2018-02-08 22:47:45 +00:00
Rob Schnautz
93185e7e84 Upload New File 2018-02-08 22:47:34 +00:00
Rob Schnautz
18a717e90a Upload New File 2018-02-08 22:47:24 +00:00
Rob Schnautz
99ce255da8 Upload New File 2018-02-08 22:47:10 +00:00
Rob Schnautz
770e328865 Upload New File 2018-02-08 22:46:59 +00:00
Rob Schnautz
2776eaf640 Upload New File 2018-02-08 22:46:48 +00:00
Rob Schnautz
2d825e4095 Upload New File 2018-02-08 22:46:34 +00:00
Rob Schnautz
64cac6cd5a Upload New File 2018-02-08 22:46:16 +00:00
Rob Schnautz
8d7eccf92e Add new directory 2018-02-08 22:45:50 +00:00
Rob Schnautz
01cd80cc2f Add new directory 2018-02-08 22:45:35 +00:00
Rob Schnautz
801e218912 +amonkhet 2018-02-08 22:44:53 +00:00
Rob Schnautz
c1cb19a139 Add new directory 2018-02-08 22:41:02 +00:00
Hans Mackowiak
419f4a354f Merge branch 'assorted-fixes' into 'master'
- Got rid of an esoteric component for logging, switched over to standard err…

See merge request core-developers/forge!177
2018-02-08 17:48:41 +00:00
Seravy
569e884a2c Adds an option to choose between draft rotation deleting, or replacing old drafts.
Also moves the draft rotation settings to appear under "draft torunaments" where they belong.
2018-02-08 14:13:11 +01:00
Agetian
647e9f6a14 - Got rid of an esoteric component for logging, switched over to standard err printout like it's done in most other places. 2018-02-08 15:28:50 +03:00
Seravy
a24cffa9df Makes the maximal amount of bonus for life total difference optional 2018-02-08 13:12:35 +01:00
Michael Kamensky
58d0921696 Merge branch 'morph' into 'master'
Morph: some rework to use better KeywordInterfaces

See merge request core-developers/forge!172
2018-02-08 12:07:45 +00:00
Michael Kamensky
2b3e39cbb8 Merge branch 'regeneration' into 'master'
Regeneration

See merge request core-developers/forge!168
2018-02-08 12:07:41 +00:00
Seravy
689e5eecc4 Add options to control how much extra credits are given for accumulated wins 2018-02-08 12:04:07 +01:00
Seravy
36917c0d18 Fixes compile/run bugs in IntelliJ 2018-02-08 10:25:17 +01:00
Michael Kamensky
4421766850 Merge branch 'descriptions' into 'master'
Descriptions for preconstructed decks, premium foil inserts

See merge request core-developers/forge!169
2018-02-08 04:51:50 +00:00
Michael Kamensky
2d2af55849 Merge branch 'patch-3' into 'master'
Update Masters 25.txt

See merge request core-developers/forge!170
2018-02-08 04:51:02 +00:00
Rob Schnautz
906fef89f9 fix rarity 2018-02-08 01:55:55 +00:00
Rob Schnautz
479a468187 Update Masters 25.txt 2018-02-08 01:54:07 +00:00
Rob Schnautz
813e8deb8f description, premium Boltwing Marauder 2018-02-07 20:04:04 +00:00
Rob Schnautz
4c4259ed90 description, premium Havengul Runebinder 2018-02-07 20:01:36 +00:00
Rob Schnautz
b186958422 premium Captivating Vampire 2018-02-07 19:58:51 +00:00
Rob Schnautz
83b41e1479 description 2018-02-07 19:57:47 +00:00
Rob Schnautz
97477644fd description, premium Phyrexian Swarmlord 2018-02-07 19:54:28 +00:00
Rob Schnautz
f3e4f0d1fb description 2018-02-07 19:51:23 +00:00
Rob Schnautz
4175369f96 description, premium Mordant Dragon 2018-02-07 19:45:02 +00:00
Rob Schnautz
f2fcfe582f description, premium Hero of Goma Fada 2018-02-07 19:42:53 +00:00
Rob Schnautz
b2d546be2f description, premium Carnival Hellsteed 2018-02-07 19:40:20 +00:00
Rob Schnautz
a7db0dd75e description 2018-02-07 19:38:14 +00:00
Rob Schnautz
94127c1b76 description 2018-02-07 19:35:37 +00:00
Rob Schnautz
220690d4d0 description, premium Hoard-Smelter Dragon 2018-02-07 19:31:26 +00:00
Hanmac
fb3ffe679b Morph: some rework to use better KeywordInterfaces 2018-02-07 19:40:12 +01:00
Rob Schnautz
b38f7fe4f5 description 2018-02-07 17:16:41 +00:00
Rob Schnautz
110eb2b072 description, premium Elder of Laurels 2018-02-07 17:03:47 +00:00
Michael Kamensky
d474e6b8bb Merge branch 'assorted-fixes' into 'master'
- Do not allow the human to confirm a Reveal cost with 0 revealed cards (e.g. Morph - Reveal a green card when the player has no green card in hand)

See merge request core-developers/forge!167
2018-02-07 16:56:21 +00:00
Rob Schnautz
5b074f4c1c description, premium Malakir Bloodwitch 2018-02-07 16:53:13 +00:00
Rob Schnautz
8e4ab4d398 description 2018-02-07 16:52:23 +00:00
Rob Schnautz
7eb934d5d3 description 2018-02-07 16:51:50 +00:00
Agetian
c3b655f675 - Do not allow the human to confirm a Reveal cost with 0 revealed cards if the minimum is more than zero (fixes e.g. Morph costs with "Reveal a X card" when the human player doesn't have that kind of card in hand). 2018-02-06 22:17:48 +03:00
Hanmac
068efaeba7 Card: RegenEffect: add the Effect as Shield 2018-02-06 13:50:04 +01:00
Hanmac
ff7a0f1ae6 Regeneration Rework:
- Regenerate or RegenerateAll does create an Effect
in Command which does replace Destroy if possible
- Trigger Regenerated will be added to the Effect
if something does care about "regenerated that way"
- new Regeneration Api is the internal effect
that does handle the actual regeneration
- ReplaceDestroy has Section to handle if Regeneration is possible
- CardShields are removed with the option in PlayerController
2018-02-06 12:56:02 +01:00
Michael Kamensky
8b26deb477 Merge branch 'assorted-fixes' into 'master'
- Fixed the AI trying to e.g. Embalm a Vizier of Many Faces ignoring NeedsToPlay

See merge request core-developers/forge!166
2018-02-05 05:34:10 +00:00
Agetian
21fedd0d50 - Fixed Illusion of Choice. 2018-02-05 08:21:02 +03:00
Agetian
ecd4a5dedd - Fixed the AI trying to e.g. Embalm a Vizier of Many Faces ignoring the NeedsToPlay SVar requirement. 2018-02-04 20:24:51 +03:00
Hanmac
047ee71259 Card: Planeswalker + Creature get extra damage
Signed-off-by: Hanmac <hanmac@gmx.de>
2018-02-02 23:52:11 +01:00
Michael Kamensky
1b97eb777c Merge branch 'mobile-release' into 'master'
- Preparing Forge for Android publish 1.6.6.003 [incremental/bugfixes].

See merge request core-developers/forge!165
2018-01-31 10:54:51 +00:00
Agetian
cd31c4e513 - Preparing Forge for Android publish 1.6.6.003 [incremental/bugfixes]. 2018-01-31 13:54:05 +03:00
Hans Mackowiak
1582f5a7bd Merge branch 'assorted-fixes' into 'master'
- Indirect aura attachment: fix Sovereigns of Lost Alara not attaching Cartouches

See merge request core-developers/forge!164
2018-01-31 06:01:00 +00:00
Agetian
32facbbc74 - Indirect aura attachment: check by aura controller if aura activating player check fails, fixes Sovereign of Lost Alara not being able to attach a Cartouche. 2018-01-31 08:00:26 +03:00
Michael Kamensky
7696b9d69c Merge branch 'assorted-fixes' into 'master'
- AiController: a more robust check for potential targets (e.g. Wicked Akuba)

See merge request core-developers/forge!161
2018-01-31 04:57:08 +00:00
Michael Kamensky
d2e1d5aacb Merge branch 'patch-3' into 'master'
Masters 25

See merge request core-developers/forge!163
2018-01-31 04:18:08 +00:00
Michael Kamensky
e70affe6c3 Merge branch 'upcoming_rix2' into 'master'
Updates to card-based deck generation data and improvements to handling of…

See merge request core-developers/forge!162
2018-01-31 04:17:05 +00:00
Rob Schnautz
d38f9c39e4 Add new file 2018-01-30 23:02:54 +00:00
austinio7116
f7209ef32c Updates to card-based deck generation data and improvements to handling of non-basic land selection in those decks 2018-01-30 22:33:40 +00:00
Agetian
1f10ba936a Merge remote-tracking branch 'origin/assorted-fixes' into assorted-fixes 2018-01-30 23:36:36 +03:00
Agetian
0332975a9a - Reset activations when cloning things with Saheeli Rai (fixes e.g. Saheeli Rai cloning a Saheeli Rai under Liquimetal Coating not able to activate its abilities) 2018-01-30 23:18:07 +03:00
Agetian
369c4f77a0 - AiController: a more robust check for potential targets in order to avoid the AI activating an ability it can't legally target due to restrictions (e.g. Wicked Akuba). 2018-01-30 19:26:14 +03:00
Michael Kamensky
d8a284ebb9 Merge branch 'unstable-basic-lands' into 'master'
- Render Unstable basic lands borderless.

See merge request core-developers/forge!160
2018-01-30 16:22:08 +00:00
Agetian
dca62ce725 - AiController: a more robust check for potential targets in order to avoid the AI activating an ability it can't legally target due to restrictions (e.g. Wicked Akuba). 2018-01-30 19:20:24 +03:00
Michael Kamensky
1c64ebd05b Merge branch 'upcoming_rix2' into 'master'
Added HOU, XLN and RIX image file paths to the lists for quest mode

See merge request core-developers/forge!159
2018-01-30 15:39:53 +00:00
Agetian
0727c83a43 - Render Unstable basic lands borderless. 2018-01-30 18:14:06 +03:00
maustin
eb4a8caefc Removed unneeded targeting of opponent in Huatli Radiant Champion 2018-01-30 12:51:53 +00:00
Michael Kamensky
253efc6c6d Merge branch 'assorted-fixes' into 'master'
Several card fixes and an AI pump prediction fix

See merge request core-developers/forge!158
2018-01-30 10:45:28 +00:00
Agetian
d8be06c165 - Fixed Battle at the Bridge SpellDescription. 2018-01-30 13:17:43 +03:00
Agetian
e02baac897 - Fixed Wandering Fumarole not gaining its {0} ability properly 2018-01-30 13:17:32 +03:00
Agetian
46f19b6079 Merge remote-tracking branch 'origin/assorted-fixes' into assorted-fixes 2018-01-30 13:12:32 +03:00
austinio7116
2f3ba53a89 Added HOU, XLN and RIX image file paths to the lists for quest mode 2018-01-30 08:56:48 +00:00
Agetian
b763e06560 - AiAttackController: Don't check targeting restrictions if the ability doesn't target. 2018-01-30 08:45:25 +03:00
Agetian
2447ea4fb0 - AiAttackController/AiBlockController: do not count activated abilities that can't target the attacker/blocker when trying to predict P/T bonus from AF Pump (fixes the AI suicide attacking/blocking with cards like Pia Nalaar) 2018-01-30 08:45:25 +03:00
Agetian
75f86352be - AiAttackController: Don't check targeting restrictions if the ability doesn't target. 2018-01-30 08:44:16 +03:00
Agetian
19dd58d1d3 - AiAttackController/AiBlockController: do not count activated abilities that can't target the attacker/blocker when trying to predict P/T bonus from AF Pump (fixes the AI suicide attacking/blocking with cards like Pia Nalaar) 2018-01-30 08:34:01 +03:00
Hanmac
68843db5b8 Card: changed how instant and Sorcery show keywords 2018-01-29 22:52:02 +01:00
Hanmac
586c2eb9b0 cards: update Buyback scripts 2018-01-29 22:50:07 +01:00
Hanmac
156cdf812d CardFactoryUtil: use : for Buyback, make some effect Secondary 2018-01-29 22:49:51 +01:00
Michael Kamensky
5e6d9749fb Merge branch 'patch-3' into 'master'
Quick Start Set: Rivals

See merge request core-developers/forge!157
2018-01-29 05:00:54 +00:00
Rob Schnautz
2392aeeb0c Add new file 2018-01-28 20:01:16 +00:00
Rob Schnautz
6d89addb67 description, premium Spellbreaker Behemoth 2018-01-28 19:42:29 +00:00
Hanmac
5a29b05e88 CardFactoryUtil: move Totem Armor to ReplacementAbilities 2018-01-28 14:40:37 +01:00
Hanmac
90b46f4c0f Card & CardState: add has functions to check for SpellAbility, Trigger and ReplacementEffects 2018-01-28 12:31:13 +01:00
Michael Kamensky
1281af0994 Merge branch 'assorted-fixes' into 'master'
- Fixed a compile time error caused by an unused import statement.

See merge request core-developers/forge!156
2018-01-28 08:47:48 +00:00
Agetian
b31069866a - Fixed a compile time error caused by an unused import statement. 2018-01-28 11:30:29 +03:00
Michael Kamensky
00f896bbb4 Merge branch 'patch-2' into 'master'
remove Code2, not needed for this.

See merge request core-developers/forge!154
2018-01-28 06:47:54 +00:00
Michael Kamensky
cd5c03926f Merge branch 'deck-descriptions' into 'master'
Deck descriptions

See merge request core-developers/forge!155
2018-01-28 06:46:20 +00:00
Rob Schnautz
11218913a4 description, premium Aegis Angel 2018-01-28 03:00:13 +00:00
Rob Schnautz
0a039e4f53 description 2018-01-28 02:40:17 +00:00
Rob Schnautz
7e37b3d760 description, premium Grove of the Guardian 2018-01-28 02:37:52 +00:00
Rob Schnautz
d577c229e3 description, premium Noosegraf Mob 2018-01-28 02:36:51 +00:00
Rob Schnautz
6cd74946e4 description 2018-01-28 02:36:06 +00:00
Rob Schnautz
d34b6428ae description 2018-01-28 02:30:18 +00:00
Rob Schnautz
e242e3f371 description 2018-01-28 02:29:47 +00:00
Rob Schnautz
4165004491 description, premium Fathom Mage 2018-01-28 02:23:41 +00:00
Rob Schnautz
d290d5fbf4 typo 2018-01-28 02:21:41 +00:00
Rob Schnautz
81c99209ce description 2018-01-28 02:15:34 +00:00
Rob Schnautz
1962aac95b description, premium Harvester of Souls 2018-01-28 02:07:03 +00:00
Rob Schnautz
60a66585f3 Update Unstable.txt 2018-01-28 02:03:38 +00:00
Rob Schnautz
d7f3842a30 don't need code2 2018-01-28 01:59:23 +00:00
Rob Schnautz
81be883bee Update Unglued.txt 2018-01-28 01:56:30 +00:00
Rob Schnautz
0dfcb225e0 use UGL for pics. no good reason to use the two-letter code. 2018-01-28 01:48:05 +00:00
Sol
06f95efd52 Merge branch 'fix-deck-view' into 'master'
Improve deck view with multi-monitor systems

See merge request core-developers/forge!153
2018-01-27 23:50:18 +00:00
Sol
0dae411333 Merge branch 'custom-deck-directories' into 'master'
Custom deck directories

See merge request core-developers/forge!152
2018-01-27 21:34:03 +00:00
Jamin W. Collins
c1e1b6f29a Improve deck view with multi-monitor systems
On Linux multi-monitor systems, the existing logic would create a new
window that was slightly smaller than the total combined display size.
The new logic creates a window that is slightly smaller than the monitor
Forge is currently on.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-01-27 09:49:13 -07:00
Michael Kamensky
a511bdbcd8 Merge branch 'assorted-fixes' into 'master'
Fixed an AI logic error in CountersPutAi

See merge request core-developers/forge!151
2018-01-27 16:35:09 +00:00
Agetian
b0037faa11 - Fixed an AI logic error in CountersPutAi which caused the AI to buff the opponent's creatures with Incremental Growth. 2018-01-27 19:34:11 +03:00
Jamin W. Collins
6dc6a7a29c add new parameters and descriptions to properties.example
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-01-27 09:23:51 -07:00
Jamin W. Collins
fd6192a88e remove trailing whitespace
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-01-27 09:12:55 -07:00
Jamin W. Collins
fcb8e1f8c9 allow for custom deck directory paths
This adds support for two additional configuration options to the
forge.profile.properties file:
 * decksDir
 * decksConstructedDir

The first sets the desired path for all decks.  The second sets the
desired path specifically for constructed decks.  This allows users to
reference an external directory that may be synced between multiple
users or systems by an external (to forge) mechanism.

Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-01-27 08:53:54 -07:00
Michael Kamensky
2da04fed3d Merge branch 'patch' into 'master'
- Updated a few scripts

See merge request core-developers/forge!150
2018-01-27 15:28:17 +00:00
swordshine
00abdccc13 - Simplied two scripts 2018-01-27 21:44:26 +08:00
swordshine
9f25644645 - Fixed Phytotitan 2018-01-27 20:39:39 +08:00
swordshine
83b84dfff7 - Gaze of the Gorgon uses delayed triggers 2018-01-27 20:25:52 +08:00
swordshine
c0f5f0567b - Fixed Glyph of Doom 2018-01-27 20:16:41 +08:00
swordshine
f53a54a126 - Fixed blockedByRemembered when two or more cards are remembered 2018-01-27 19:56:41 +08:00
swordshine
e3ac4433e1 - Venomous Breath now uses delayed triggers 2018-01-27 19:39:20 +08:00
swordshine
1e831513a0 - Shifty Doppelganger now uses delayed triggers 2018-01-27 18:53:20 +08:00
swordshine
a40a6e8023 - Rescue from the Underworld now uses delayed triggers 2018-01-27 17:00:38 +08:00
swordshine
22982178e2 - Attempt to fix the CopyPermanent effect when Crafty Cutpurse is in play 2018-01-27 16:23:55 +08:00
swordshine
63e2854d7a - It should be a special action to remove the effect of Glass Asp (these effects might be transformed to delayed triggers in the future) 2018-01-27 14:53:27 +08:00
swordshine
7117580446 - Some keywords should be "hidden" 2018-01-27 14:42:33 +08:00
swordshine
691562324b - Giant Slug now uses a delayed trigger 2018-01-27 10:40:31 +08:00
swordshine
f0e6e20732 - Elkin Bottle should have a duration 2018-01-27 09:31:25 +08:00
Michael Kamensky
67ab3e1e01 Merge branch 'patch' into 'master'
- Necromancy and its friends now use delayed triggers instead of animated triggers

See merge request core-developers/forge!149
2018-01-26 16:54:50 +00:00
Hans Mackowiak
a048ab3327 Merge branch 'assorted-fixes' into 'master'
Fixed CardState#copyFrom copying non-intrinsic abilities, minor card fix (Nissa, Steward of Elements)

See merge request core-developers/forge!148
2018-01-26 14:13:22 +00:00
swordshine
af1be79f78 - Necromancy and its friends now use delayed triggers instead of animated triggers 2018-01-26 19:24:29 +08:00
Agetian
81366a1e14 Merge remote-tracking branch 'origin/assorted-fixes' into assorted-fixes 2018-01-26 13:46:23 +03:00
Agetian
21c4e2eeda - Fixed Nissa, Steward of Elements references and AI logic. 2018-01-26 13:36:48 +03:00
Agetian
b866338197 - Only copy the intrinsic abilities in CardState#copyFrom, fixes clone abilities copying too much (e.g. copied tokens from Splinter Twin gaining the copy ability). 2018-01-26 09:16:13 +03:00
Agetian
98e19db3fc Merge branch 'master' of git.cardforge.org:core-developers/forge into assorted-fixes 2018-01-26 09:08:14 +03:00
Agetian
5221d871a0 - Only copy the intrinsic abilities in CardState#copyFrom, fixes clone abilities copying too much (e.g. copied tokens from Splinter Twin gaining the copy ability). 2018-01-26 09:07:50 +03:00
Sol
d48c93938f Merge branch 'patch-2' into 'master'
Simplify string generation in a few files

See merge request core-developers/forge!128
2018-01-26 01:51:30 +00:00
Michael Kamensky
0aa3583412 Merge branch 'assorted-fixes' into 'master'
- A couple AI tweaks and fixes, several card fixes

See merge request core-developers/forge!144
2018-01-25 15:42:32 +00:00
Michael Kamensky
38adbb7386 Merge branch 'patch-3' into 'master'
Fix spelling error in Might Makes Right quest opponent decks

See merge request core-developers/forge!147
2018-01-25 04:48:29 +00:00
Rob Schnautz
0ecdb07a0a description 2018-01-25 02:54:04 +00:00
Rob Schnautz
5191777fca description 2018-01-25 02:48:46 +00:00
Rob Schnautz
e1feff66b4 description 2018-01-25 02:42:42 +00:00
Rob Schnautz
e96f0e24b3 description, premium Nefarox, Overlord of Grixis 2018-01-25 02:26:19 +00:00
Rob Schnautz
891b2580c0 description 2018-01-25 02:23:12 +00:00
Rob Schnautz
8fa477a3f2 description 2018-01-25 02:22:16 +00:00
Rob Schnautz
48079df235 description 2018-01-25 02:21:38 +00:00
Rob Schnautz
4bd796c84b description 2018-01-25 02:21:00 +00:00
Rob Schnautz
efa38ea80a description, premium Lone Revenant 2018-01-25 02:20:21 +00:00
Rob Schnautz
b0be025f93 description 2018-01-25 02:18:14 +00:00
Rob Schnautz
08bc3170ad description 2018-01-25 02:17:12 +00:00
Rob Schnautz
b126a97e23 description, premium Angel of Flight Alabaster 2018-01-25 02:15:59 +00:00
Rob Schnautz
505940d9b4 description, premium Overwhelming Stampede 2018-01-25 02:12:21 +00:00
Rob Schnautz
41fbedbdb9 description, premium Flamerush Rider 2018-01-25 02:07:09 +00:00
Rob Schnautz
00379f8cd0 description, premium Rakshasa Vizier 2018-01-25 00:36:59 +00:00
Rob Schnautz
52e9772ef9 description 2018-01-25 00:32:48 +00:00
Rob Schnautz
634a9e7a6c description 2018-01-25 00:27:35 +00:00
Rob Schnautz
8383aeeddd Description + premium Tyrant of Valakut 2018-01-25 00:26:49 +00:00
Rob Schnautz
308c07c2b3 description 2018-01-25 00:25:31 +00:00
Rob Schnautz
ad352beb24 typo 2018-01-25 00:20:25 +00:00
Agetian
1e59b75d08 - Don't order cards from Discard<0/Hand> if there's only one card going to graveyard.
- Allow to order the cards going to graveyard from Discard<X/Random>.
2018-01-24 16:53:30 +03:00
Agetian
bfef8b125c - Fixed the cost "discard your hand" not allowing to order the cards going to graveyard when and if needed. 2018-01-24 16:41:12 +03:00
Agetian
b3f1bad6b7 - Fixed Jungle Creeper. 2018-01-24 16:02:11 +03:00
Agetian
8e3e38b72f Merge branch 'master' of git.cardforge.org:core-developers/forge into assorted-fixes 2018-01-24 09:15:49 +03:00
Agetian
714f21913b - Method name unification. 2018-01-24 09:14:08 +03:00
Agetian
6e1733821c - Somewhat better logic for Glimmer of Genius, part 2. 2018-01-24 09:07:00 +03:00
Hans Mackowiak
d5c285bb63 Merge branch 'bestowfix' into 'master'
Spell: Bestow now works with StaticAbilities again

See merge request core-developers/forge!145
2018-01-24 05:41:04 +00:00
Agetian
2be734f137 - Somewhat better logic for Glimmer of Genius. 2018-01-24 08:23:28 +03:00
Agetian
67ab92359f Merge branch 'master' of git.cardforge.org:core-developers/forge into assorted-fixes 2018-01-24 08:11:59 +03:00
Agetian
89184b97dd - ScryAi: Glimmer of Genius -> at opp EOT 2018-01-23 23:50:48 +03:00
Hanmac
466eac71a1 Spell: Bestow now works with StaticAbilities again 2018-01-23 21:32:45 +01:00
Agetian
ce35c6232e - Simple AI logic for Explore with a target (e.g. Enter the Unknown), fixes the AI wasting its activation. 2018-01-23 18:17:36 +03:00
Agetian
2e51375c0f - Somewhat less risky logic for the AI Glimmer of Genius (otherwise it manalocks itself too much) 2018-01-23 18:03:31 +03:00
Michael Kamensky
e38b34e391 Merge branch 'master' into 'master'
- Preparing Forge for Android publish 1.6.6.002 [hotfix].

See merge request core-developers/forge!143
2018-01-23 08:06:39 +00:00
Agetian
2e0af3c3fe - Preparing Forge for Android publish 1.6.6.002 [hotfix]. 2018-01-23 11:05:17 +03:00
Michael Kamensky
ab1550e24b Merge branch 'master' into 'master'
- FIXME: Temporarily disabling preList in a checkStaticAbilities call for Bestow to avoid P/T corruption and flickering

See merge request core-developers/forge!142
2018-01-23 08:03:15 +00:00
Agetian
7d57af98fd - FIXME: Temporarily disabling preList in a checkStaticAbilities call for Bestow (causes P/T flickering on cards and related game state corruption, resulting in unwanted P/T changes between game phases). 2018-01-23 10:57:48 +03:00
Michael Kamensky
f1cb6b7b8e Merge branch 'patch' into 'master'
- Fixed two more cards with delayed triggers

See merge request core-developers/forge!141
2018-01-23 07:03:38 +00:00
swordshine
2cb03f1671 - Fixed two more cards with delayed triggers 2018-01-23 14:56:55 +08:00
Michael Kamensky
da68b22601 Merge branch 'master' into 'master'
Fixed Oracle text for Hanweir Battlements

See merge request core-developers/forge!140
2018-01-22 19:39:18 +00:00
Agetian
c68f1b8a03 - Fixed Oracle text for Hanweir Battlements. 2018-01-22 22:23:49 +03:00
Michael Kamensky
02ae5e301d Merge branch 'patch' into 'master'
- Fixed a few cards

See merge request core-developers/forge!138
2018-01-22 14:02:23 +00:00
Michael Kamensky
0dc7a4af2d Merge branch 'upcoming_rix2' into 'master'
Improved AI handling of Kumena

See merge request core-developers/forge!139
2018-01-22 13:50:07 +00:00
austinio7116
4d8bb5a844 Improved AI handling of Kumena 2018-01-22 08:33:33 +00:00
swordshine
a8ffdecef0 - Fixed Mountain Titan 2018-01-22 15:19:43 +08:00
swordshine
adf62cc5f5 - Fixed Cauldron Dance 2018-01-22 15:02:52 +08:00
Rob Schnautz
4aaafdf106 Description 2018-01-22 05:42:11 +00:00
Rob Schnautz
95e5f06554 fix typo 2018-01-22 05:00:37 +00:00
Michael Kamensky
361604d7f6 Merge branch 'patch-8' into 'master'
Several deck updates - descriptions and premium cards

See merge request core-developers/forge!137
2018-01-22 04:41:07 +00:00
Sol
97292d5a7f Merge branch 'patch-1' into 'master'
Fix for issue #124

See merge request core-developers/forge!125
2018-01-22 04:33:54 +00:00
Rob Schnautz
3a552823c8 Description 2018-01-22 01:55:38 +00:00
Rob Schnautz
a36c06ff2f Description, premium Drowner of Hope 2018-01-22 01:46:10 +00:00
Rob Schnautz
fe7cce1726 Description 2018-01-22 01:27:37 +00:00
Rob Schnautz
b4e5313160 Description 2018-01-22 01:20:10 +00:00
Rob Schnautz
151fb40e23 Description, premium Alhammarret, High Arbiter 2018-01-22 01:17:17 +00:00
Rob Schnautz
a111341293 Description 2018-01-22 01:15:04 +00:00
Rob Schnautz
4df7554d81 Description 2018-01-22 01:07:06 +00:00
Rob Schnautz
e58b0c219f Description 2018-01-22 01:05:18 +00:00
Rob Schnautz
e551c98ca4 Description 2018-01-22 01:02:57 +00:00
Rob Schnautz
0bd954e0c1 Description 2018-01-22 00:58:39 +00:00
Rob Schnautz
23f13ca87e Description 2018-01-22 00:55:06 +00:00
Rob Schnautz
2f66df36a7 Description 2018-01-22 00:52:30 +00:00
Rob Schnautz
03cc62f4df Description 2018-01-22 00:35:17 +00:00
Rob Schnautz
31fb23b83b Description 2018-01-22 00:31:16 +00:00
Rob Schnautz
445c87812d Description 2018-01-22 00:26:47 +00:00
Rob Schnautz
25b3ffbb6e Description 2018-01-22 00:23:25 +00:00
Rob Schnautz
358f5c6046 Description 2018-01-22 00:18:34 +00:00
Rob Schnautz
4fdc38b53f Description 2018-01-21 23:18:32 +00:00
Rob Schnautz
9fc669d897 Description 2018-01-21 21:00:33 +00:00
Michael Kamensky
f40f868429 Merge branch 'patch-7' into 'master'
Description, premium Sanctifier of Souls

See merge request core-developers/forge!136
2018-01-21 19:58:08 +00:00
Michael Kamensky
43bdd23b3c Merge branch 'patch-6' into 'master'
Description, premium Dragonscale General

See merge request core-developers/forge!135
2018-01-21 19:47:49 +00:00
Rob Schnautz
0bf12f0965 Description 2018-01-21 19:45:34 +00:00
Rob Schnautz
5434605984 Premium Heroes' Bane 2018-01-21 19:44:49 +00:00
Rob Schnautz
19a1bf0c0e Premium Gigantomancer 2018-01-21 19:39:34 +00:00
Rob Schnautz
16adb81cd2 Description, premium Deepfathom Skulker 2018-01-21 19:35:20 +00:00
Rob Schnautz
115b77682b Description, premium Nephalia Moondrakes 2018-01-21 19:26:57 +00:00
Rob Schnautz
82839cc500 Description, premium Sanctifier of Souls 2018-01-21 19:21:08 +00:00
Rob Schnautz
5dca460399 Description, premium Dragonscale General 2018-01-21 19:15:33 +00:00
Michael Kamensky
0eeb68737c Merge branch 'patch-4' into 'master'
Premium Sphinx of Jwar Isle

See merge request core-developers/forge!133
2018-01-21 19:15:10 +00:00
Michael Kamensky
389e21aca7 Merge branch 'patch-3' into 'master'
Update Untamed Wild.dck

See merge request core-developers/forge!132
2018-01-21 19:14:30 +00:00
Michael Kamensky
1eec2905b1 Merge branch 'patch-5' into 'master'
Premium Lich Lord of Unx

See merge request core-developers/forge!134
2018-01-21 19:13:22 +00:00
Rob Schnautz
7823368ebc Premium Lich Lord of Unx 2018-01-21 19:05:57 +00:00
Rob Schnautz
8b0f5ff91e Premium Sphinx of Jwar Isle 2018-01-21 18:36:42 +00:00
Rob Schnautz
f45a4d06a3 Update Untamed Wild.dck 2018-01-21 18:33:59 +00:00
Michael Kamensky
d16a197651 Merge branch 'assorted-fixes' into 'master'
- Fixed the AI for Hunt the Weak/Savage Storm, prevented a crash when trying to exec a trigger with a non-existent SVar

See merge request core-developers/forge!130
2018-01-21 18:04:27 +00:00
Hanmac
95263720f3 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-01-21 17:56:35 +01:00
Hanmac
56f05c54f9 CardFactoryUtil: fixed Haunt for non creature 2018-01-21 17:53:45 +01:00
Agetian
b20c5acf75 - Issue a warning when trying to execute a trigger that references a SVar which does not exist and abort the execution of that trigger instead of hard-crashing the game (makes the issue with a manifested Hideaway land dying non-fatal). 2018-01-21 19:49:04 +03:00
Michael Kamensky
33f296da7c Merge branch 'newcard' into 'master'
- Daretti, Ingenious Iconoclast: Cleanup remembered for the second ability

See merge request core-developers/forge!131
2018-01-21 16:46:29 +00:00
Rob Schnautz
429ac3c072 TextUtil not needed in this file. 2018-01-21 16:17:13 +00:00
Agetian
4b5a555c1e - Fixed the AI overtargeting cards like Hunt the Weak and Savage Stomp and failing to add the card to stack. 2018-01-21 16:38:45 +03:00
swordshine
278d64d800 - Daretti, Ingenious Iconoclast: Cleanup remembered for the second ability 2018-01-21 20:24:30 +08:00
Michael Kamensky
6ceb08d41d Merge branch 'mobile-release' into 'master'
- Preparing Mobile Forge publish v1.6.6.001 [incremental].

See merge request core-developers/forge!129
2018-01-21 12:22:50 +00:00
Agetian
0e513614c3 - Preparing Mobile Forge publish v1.6.6.001 [incremental]. 2018-01-21 15:16:59 +03:00
Hans Mackowiak
b8ac0819aa Merge branch 'Keywordcache' into 'master'
Cache keywords to fix the slowdown issues on mobile Forge

See merge request core-developers/forge!127
2018-01-21 07:31:04 +00:00
Rob Schnautz
8380da7a10 Don't need transformPT anymore. 2018-01-21 04:59:46 +00:00
Rob Schnautz
cb7922fe15 Don't need TextUtils here 2018-01-21 04:57:51 +00:00
Rob Schnautz
3b6cfef67d TextUtil isn't needed here. 2018-01-21 04:46:22 +00:00
Rob Schnautz
b20bb2d023 simplify strings 2018-01-20 21:04:08 +00:00
Rob Schnautz
f8decaac41 simplify strings 2018-01-20 20:51:37 +00:00
Rob Schnautz
d7674c15b5 Update CardFace.java 2018-01-20 20:45:06 +00:00
Rob Schnautz
9d1c6eaa7f simplify string generation 2018-01-20 20:34:01 +00:00
Michael Kamensky
1927a94879 Merge branch 'assorted-fixes' into 'master'
- Fixed Oathsworn Vampire ability description.

See merge request core-developers/forge!126
2018-01-20 20:27:20 +00:00
Rob Schnautz
590b877f76 simplify string generation 2018-01-20 19:59:28 +00:00
Agetian
6c93862891 Merge branch 'master' of git.cardforge.org:core-developers/forge into assorted-fixes 2018-01-20 22:44:48 +03:00
Agetian
cd9289bcdd - Fixed Oathsworn Vampire ability description. 2018-01-20 22:23:14 +03:00
Hanmac
c294e1f6df Card cached keywords move to CardState, tests still run 2018-01-20 18:52:34 +01:00
Rob Schnautz
9090cc70bf Probably want to use err, not out. 2018-01-20 15:41:55 +00:00
Rob Schnautz
244e2a9e8d Remove concatWithSpace since there's nothing to concatenate anymore. 2018-01-20 15:33:11 +00:00
Rob Schnautz
cc2b2921d9 Move the log output here to make it display the correct edition. 2018-01-20 15:02:24 +00:00
Rob Schnautz
88339864f7 The edition displayed isn't correct. Moving that part of the output to the class that creates the "unsupported card" object. 2018-01-20 14:57:44 +00:00
Michael Kamensky
f0ad9083a5 Merge branch 'volrath-fix' into 'master'
Fixed Volrath's Shapeshifter

See merge request core-developers/forge!124
2018-01-20 11:58:37 +00:00
Hanmac
e66194fb9d Card: getUnhiddenKeywords uses cache
that is updated by CardStateView#updateKeywords
2018-01-20 08:09:56 +01:00
Agetian
042693bf53 - Volrath's Shapeshifter: unfortunately, the generic solution for AB Discard on a SVar didn't work (doesn't work with cloning, crashes the game), so resorting to a non-generic version specific to VS. 2018-01-19 22:16:16 +03:00
Michael Kamensky
4a8cecd225 Merge branch 'assorted-fixes' into 'master'
Various minor card and definition fixes.

See merge request core-developers/forge!123
2018-01-19 19:04:14 +00:00
Agetian
1a9003c999 - Trim the spaces on TokenAltImages. 2018-01-19 21:51:20 +03:00
Michael Kamensky
e35f09ea2f Merge branch 'angrath-fix' into 'master'
- Fixed Angrath, the Flame-Chained [-3] ability.

See merge request core-developers/forge!120
2018-01-19 18:42:15 +00:00
Agetian
056d66b865 - Apparently the extra copy for activated ABs is no longer necessary for Volrath's Shapeshifter. 2018-01-19 21:40:36 +03:00
Agetian
ceafaf5e2e - Fixing Volrath's Shapeshifter, pass 2. 2018-01-19 21:31:45 +03:00
Agetian
5207e697dd - Fixing Volrath's Shapeshifter, pass 1. 2018-01-19 12:06:14 +03:00
Agetian
39a5da9a1b - Fixed Sporogenesis. 2018-01-19 07:45:30 +03:00
Agetian
4ed99e026f - Fixed Sword Guardian rarity in RIX def file. 2018-01-19 07:44:17 +03:00
Agetian
cdff15dade - Fixed TokenAltImages definition in Awakening Zone. 2018-01-19 07:44:00 +03:00
Michael Kamensky
16f2b16d22 Merge branch 'patch-3' into 'master'
DDT - Add periods in "vs."

See merge request core-developers/forge!122
2018-01-19 04:39:24 +00:00
Michael Kamensky
3efde7a2e3 Merge branch 'patch-2' into 'master'
Token image updates for cards beginning with A

See merge request core-developers/forge!113
2018-01-19 04:33:42 +00:00
Sol
032bcec313 Merge branch 'upcoming_rix2' into 'master'
Fixed TriggerZones for Storm the Vault plus corrected description on trigger

See merge request core-developers/forge!121
2018-01-19 03:05:59 +00:00
Rob Schnautz
a5d5ed8524 Add periods in "vs." 2018-01-18 23:53:24 +00:00
Rob Schnautz
c1e5d346c8 fix syntax 2018-01-18 22:03:04 +00:00
austinio7116
0a004b8f7e Fixed TriggerZones for Storm the Vault plus corrected description on trigger 2018-01-18 20:51:55 +00:00
Agetian
ffcd5b2f2c - Fixed Angrath, the Flame-Chained. 2018-01-18 18:57:51 +03:00
Sol
11ed680b5a Merge branch 'assorted-fixes' into 'master'
- Fixed some RIX cards, some long PTK quest deck file names, and updated CHANGES.txt.

See merge request core-developers/forge!111
2018-01-18 13:37:41 +00:00
Agetian
3abc22a855 - Attempted to fix Angrath, the Flame-Chained [-3] ability. 2018-01-18 11:38:48 +03:00
Agetian
ef9d6203c0 - Updating CHANGES.txt to reflect recent changes. 2018-01-18 09:15:19 +03:00
Agetian
016df27726 Merge remote-tracking branch 'origin/assorted-fixes' into assorted-fixes 2018-01-18 09:10:19 +03:00
Agetian
246ce114e2 - Rename some long deck file names in PTK quest to satisfy tar requirements for packaging. 2018-01-18 09:09:44 +03:00
Michael Kamensky
f3767417b0 Merge branch 'patch-3' into 'master'
Anthologies

See merge request core-developers/forge!114
2018-01-18 05:13:52 +00:00
Michael Kamensky
67fb62ec52 Merge branch 'patch-4' into 'master'
Battle Royale

See merge request core-developers/forge!115
2018-01-18 05:13:43 +00:00
Michael Kamensky
27f2dcc8d5 Merge branch 'patch-5' into 'master'
Beatdown

See merge request core-developers/forge!116
2018-01-18 05:13:26 +00:00
Michael Kamensky
9f37393a69 Merge branch 'patch-6' into 'master'
Deckmasters: Garfield vs. Finkel

See merge request core-developers/forge!117
2018-01-18 05:13:05 +00:00
Michael Kamensky
a7af6ecfc0 Merge branch 'patch-7' into 'master'
Duels of the Planeswalkers

See merge request core-developers/forge!118
2018-01-18 05:12:54 +00:00
Michael Kamensky
f772ab5fcc Merge branch 'newcard' into 'master'
- Fixed Palace Jailer

See merge request core-developers/forge!119
2018-01-18 05:12:26 +00:00
swordshine
6076954881 - Fixed Palace Jailer 2018-01-18 09:21:53 +08:00
Rob Schnautz
581bb74b9c revert 2018-01-17 21:17:06 +00:00
Rob Schnautz
81a8c93db4 revert 2018-01-17 21:16:45 +00:00
Rob Schnautz
6d8e162df3 revert 2018-01-17 21:16:36 +00:00
Rob Schnautz
9f44b46186 revert 2018-01-17 21:16:32 +00:00
Hanmac
e15f826190 Blessing: need to do blessing before condition 2018-01-17 21:04:44 +01:00
Rob Schnautz
f5ea2c0345 revert 2018-01-17 20:03:17 +00:00
Rob Schnautz
fff0051540 revert 2018-01-17 19:59:02 +00:00
Rob Schnautz
0fd3df021a revert 2018-01-17 19:58:09 +00:00
Rob Schnautz
44bbcfeebb revert 2018-01-17 19:57:08 +00:00
Rob Schnautz
5ae8e4c391 revert 2018-01-17 19:56:48 +00:00
Rob Schnautz
1e039cefcb revert 2018-01-17 19:52:31 +00:00
Rob Schnautz
eeafd84625 revert 2018-01-17 19:51:06 +00:00
Rob Schnautz
6955ad861c revert 2018-01-17 19:46:25 +00:00
Rob Schnautz
9fff6d70fe revert 2018-01-17 19:41:51 +00:00
Rob Schnautz
742e718c43 revert 2018-01-17 19:39:50 +00:00
Hanmac
d3020d591c StaticAbilityContinuous: use preList to check defined list other than zones 2018-01-17 20:34:24 +01:00
Rob Schnautz
ab44402886 revert 2018-01-17 19:31:15 +00:00
Hanmac
13cf87fa7a Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-01-17 20:28:53 +01:00
Rob Schnautz
c9a2ccb9f9 revert to INV 2018-01-17 19:13:37 +00:00
Rob Schnautz
3f78a5d595 missing pipe 2018-01-17 19:01:41 +00:00
Rob Schnautz
cdfdcba477 Duels of the Planeswalkers 2018-01-17 18:04:21 +00:00
Rob Schnautz
1dbbfb5112 Deckmasters: Garfield vs. Finkel 2018-01-17 17:42:47 +00:00
Rob Schnautz
d528d3854b Beatdown 2018-01-17 17:39:35 +00:00
Rob Schnautz
f6b5467663 add missing .txt extension 2018-01-17 17:37:02 +00:00
Rob Schnautz
77e22b355b add missing .txt extension 2018-01-17 17:36:22 +00:00
Rob Schnautz
74b2b1d4cf Battle Royale 2018-01-17 17:18:27 +00:00
Rob Schnautz
9736d6c3e4 Change Reprint to Other since it's a box set 2018-01-17 17:14:48 +00:00
Rob Schnautz
4c8f1be770 Anthologies 2018-01-17 17:06:12 +00:00
Agetian
3f88293d8e Merge branch 'master' of git.cardforge.org:core-developers/forge into assorted-fixes 2018-01-17 18:26:27 +03:00
Michael Kamensky
40828d8f57 Merge branch 'newcard' into 'master'
- Added three cards

See merge request core-developers/forge!112
2018-01-17 15:21:46 +00:00
swordshine
c97d67e4fc - Added Imprison 2018-01-17 18:55:15 +08:00
swordshine
e3265ace4b - Added Palace Jailer 2018-01-17 18:32:08 +08:00
Rob Schnautz
2e3afcbe45 PCA tokens 2018-01-17 07:16:14 +00:00
Rob Schnautz
236ac800d6 DDP tokens 2018-01-17 07:10:16 +00:00
Rob Schnautz
d395c47d4d C16 tokens 2018-01-17 07:08:44 +00:00
Rob Schnautz
178944b3da MM3 tokens 2018-01-17 07:07:27 +00:00
Rob Schnautz
61457cbba8 ODY tokens 2018-01-17 07:06:22 +00:00
Rob Schnautz
a74d4ab0c6 C16 tokens 2018-01-17 07:00:49 +00:00
Rob Schnautz
3f561f004a C16 tokens, remove a period 2018-01-17 06:59:04 +00:00
Rob Schnautz
26a3d1b3be MM2 tokens 2018-01-17 06:53:59 +00:00
Hanmac
a4e619fba2 CardPredicates: add inZone check 2018-01-17 07:40:03 +01:00
Rob Schnautz
f6230e76fd IMA tokens 2018-01-17 06:38:52 +00:00
Rob Schnautz
5a7f35c6ab DDS tokens 2018-01-17 06:25:59 +00:00
Rob Schnautz
1c5c3eba74 C16 tokens 2018-01-17 06:19:13 +00:00
Rob Schnautz
59569a9bfc C15 tokens 2018-01-17 06:17:47 +00:00
Rob Schnautz
9c7e5ae8dd M11 tokens 2018-01-17 06:16:28 +00:00
Rob Schnautz
bd0049ef57 Use M14 tokens 2018-01-17 06:15:10 +00:00
Rob Schnautz
f936f443ea use C14 tokens 2018-01-17 06:13:48 +00:00
Rob Schnautz
4d85237f44 was reprinted in VMA, use VMA tokens 2018-01-17 06:12:28 +00:00
Rob Schnautz
1cf8a96c3d EVE tokens 2018-01-17 06:08:46 +00:00
Rob Schnautz
99242384f5 use DGM tokens 2018-01-17 06:07:09 +00:00
Rob Schnautz
aaed05bef8 use MRD tokens 2018-01-17 06:02:00 +00:00
Rob Schnautz
857e3fb4a1 normalize token image name 2018-01-17 05:58:16 +00:00
swordshine
c0ae7f42d9 - Added Orzhov Advokist (still need improved ai logic) 2018-01-17 13:47:03 +08:00
Rob Schnautz
37605ca2b8 ROE tokens 2018-01-17 05:42:26 +00:00
Rob Schnautz
89ecbda923 INV tokens 2018-01-17 05:10:36 +00:00
Rob Schnautz
dc24cabcbf INV tokens 2018-01-17 05:02:36 +00:00
Michael Kamensky
ae2d1a438c Update tomb_robber.txt (the TODO line is not strictly necessary, RemAIDeck is already a visible enough flag of AI-related issues) 2018-01-17 05:01:20 +00:00
Rob Schnautz
b85917140d RTR tokens 2018-01-17 04:59:41 +00:00
Rob Schnautz
4373e81508 OGW tokens 2018-01-17 04:58:53 +00:00
Rob Schnautz
d1293259e7 normalize token image name 2018-01-17 04:37:59 +00:00
Rob Schnautz
381304867b use ISD tokens 2018-01-17 04:34:07 +00:00
Rob Schnautz
1b4b5752aa Use EVG tokens 2018-01-17 04:25:09 +00:00
Rob Schnautz
94b3ae3715 Use SOM tokens 2018-01-17 04:22:22 +00:00
Rob Schnautz
b478099a2b Use either RTR token 2018-01-17 04:18:39 +00:00
Rob Schnautz
26d8419678 normalize token image name 2018-01-17 04:11:33 +00:00
Rob Schnautz
1eb2cbf275 use INV tokens 2018-01-17 03:59:34 +00:00
Rob Schnautz
e1f3c3ad1c use BFZ tokens 2018-01-17 03:55:41 +00:00
Rob Schnautz
98c3787ed4 normalize token image 2018-01-17 03:54:27 +00:00
Rob Schnautz
97fcce87c4 use ODY tokens 2018-01-17 03:50:51 +00:00
Agetian
1536996a02 - Fixed Mausoleum Harpy. 2018-01-17 06:49:48 +03:00
Rob Schnautz
542bc66f86 Use ODY tokens 2018-01-17 03:48:38 +00:00
Rob Schnautz
206d1639b5 Use OGW tokens 2018-01-17 03:40:13 +00:00
Rob Schnautz
3a9ae627db Normalize token reference 2018-01-17 03:32:25 +00:00
Agetian
831b5a8ba3 - Fixed Tomb Robber and Jadecraft Artisan oracle texts, marked Tomb Robber as currently AI-unplayable (needs a special ExploreAi logic). 2018-01-16 22:40:56 +03:00
Sol
9b4f423afa Merge branch 'assorted-fixes' into 'master'
Assorted fixes (Oracle text correction, Tendershoot Dryad fix, Planeswalkers in PW precons should be foil)

See merge request core-developers/forge!110
2018-01-16 14:56:35 +00:00
Agetian
e7ce5bcdb6 - Fixed Oracle text for Sun-Crested Pterodon. 2018-01-16 13:04:49 +03:00
Agetian
73bbb0d3dd - Updating CHANGES.txt. 2018-01-16 09:06:33 +03:00
Agetian
1e5d2ef239 Merge branch 'master' of git.cardforge.org:core-developers/forge into assorted-fixes 2018-01-16 09:05:38 +03:00
Michael Kamensky
07b25b78ee Merge branch 'automatic_image_downloader' into 'master'
Automatic image downloader

See merge request core-developers/forge!107
2018-01-16 05:43:30 +00:00
Michael Kamensky
e67518d0f0 Merge branch 'patch-4' into 'master'
Card Workshop script loading fix for cards with punctuation and upcoming folder

See merge request core-developers/forge!109
2018-01-16 05:26:01 +00:00
Agetian
c1b7c46eca - Fixed Oracle text in Thunderherd Migration. 2018-01-16 08:25:15 +03:00
Agetian
d2d43aea2e Merge remote-tracking branch 'origin/assorted-fixes' into assorted-fixes 2018-01-16 08:24:12 +03:00
Agetian
1fa83a2d84 - Planeswalkers in planeswalker decks should be foil. 2018-01-16 08:17:52 +03:00
Agetian
6d1f698882 Merge branch 'master' of git.cardforge.org:core-developers/forge into assorted-fixes 2018-01-16 08:15:04 +03:00
Agetian
3c4a34daaa - Fixed Tendershoot Dryad. 2018-01-16 08:12:31 +03:00
Michael Kamensky
590b487711 Merge branch 'patch-2' into 'master'
Update planeswalker decks with specific basic land printings, holo PWs

See merge request core-developers/forge!106
2018-01-16 04:51:59 +00:00
Michael Kamensky
7344c934a4 Merge branch 'patch-3' into 'master'
Update planeswalker-achievements.txt (RIX)

See merge request core-developers/forge!108
2018-01-16 04:51:17 +00:00
Rob Schnautz
9a67bc0f49 add support for split cards in card workshop 2018-01-16 04:19:02 +00:00
Rob Schnautz
cbc5dc79a0 remove punctuation from filename to match the format of three other cards with exclamation points in their names 2018-01-16 04:06:19 +00:00
Rob Schnautz
1fcde1cd4b Add support for "upcoming" folder in Card Workshop, and better filename calculation. 2018-01-16 04:04:40 +00:00
Rob Schnautz
a16e398feb Update planeswalker-achievements.txt (RIX) 2018-01-16 03:01:08 +00:00
Chris H
25ca71a97a Add Picture work to Changes file 2018-01-15 17:10:53 -05:00
Chris H
23618de189 Improve LQ Pictures Downloader 2018-01-15 17:02:33 -05:00
Chris H
43455723f6 Check for existing images in a single IO request. 2018-01-15 17:02:31 -05:00
Chris H
d646240c63 Update magiccards.info protocol 2018-01-15 17:02:30 -05:00
Chris H
d60ee9307d Grab LQ picture from primary server 2018-01-15 17:02:29 -05:00
Chris H
c2915d909d Update auto-fetcher to use Set LQ Pictures 2018-01-15 17:02:28 -05:00
Rob Schnautz
3187969200 Merge branch 'patch-3' into 'patch-2'
Update Vraska, Scheming Gorgon.dck

See merge request schnautzr/forge!2
2018-01-15 21:59:37 +00:00
Rob Schnautz
30f18cd947 Update Vraska, Scheming Gorgon.dck 2018-01-15 21:58:02 +00:00
Rob Schnautz
181a1d2f1a Update Angrath, Minotaur Pirate.dck 2018-01-15 21:53:28 +00:00
Michael Kamensky
3404d267f7 Merge branch 'manually-update-release-files' into 'master'
Manually update Pom files and release changes to be ready for next release

See merge request core-developers/forge!104
2018-01-15 19:50:47 +00:00
Michael Kamensky
0af6b92e9f Merge branch 'patch-2' into 'master'
Drafting correction and booster lands correction

See merge request core-developers/forge!105
2018-01-15 19:49:00 +00:00
Chris H
c87a2bff23 Manually update Pom files and release changes to be ready for next release 2018-01-15 13:57:46 -05:00
Rob Schnautz
32a42cf9ec Add basic lands in RIX. Assuming equal distribution, haven't seen anywhere that details the odds. 2018-01-15 18:57:17 +00:00
Rob Schnautz
e2189ea877 Fix drafting and basic lands 2018-01-15 18:49:07 +00:00
Sol
fe31d692f2 Merge branch 'patch-1' into 'master'
Update formats.txt, effective 1/19/18 with release of RIX.

See merge request core-developers/forge!103
2018-01-15 16:24:57 +00:00
Rob Schnautz
84c2ac918b Update formats.txt, effective 1/19/18 with release of RIX. 2018-01-15 16:17:51 +00:00
Sol
6cf97aa2fc Merge branch 'rix-cards' into 'master'
Added Golden Guardian [RIX]

See merge request core-developers/forge!102
2018-01-15 13:48:13 +00:00
Agetian
b58a561863 - Updating CHANGES.txt. 2018-01-15 16:02:01 +03:00
Agetian
06c13804a4 Merge branch 'master' of git.cardforge.org:core-developers/forge into rix-cards 2018-01-15 16:01:13 +03:00
Agetian
bf8e88f76b - Added Golden Guardian [RIX]. Will eventually need to be reworked to use AF DelayedTrigger. 2018-01-15 16:00:52 +03:00
Michael Kamensky
84528b5251 Merge branch 'newcard' into 'master'
- Merieke Ri Berit's ability has a delayed trigger

See merge request core-developers/forge!101
2018-01-15 12:18:26 +00:00
Sol
f76428b668 Merge branch 'two-explores' into 'master'
Fix Jadelight Ranger description

See merge request core-developers/forge!100
2018-01-15 04:31:41 +00:00
swordshine
d8abdf1a2e - Merieke Ri Berit's ability has a delayed trigger 2018-01-15 09:25:10 +08:00
Chris H
f9f83660f5 Fix Jadelight Ranger description 2018-01-14 17:55:24 -05:00
Michael Kamensky
ddfd97c395 Merge branch 'upcoming_rix2' into 'master'
Added token image for rekindling phoenix

See merge request core-developers/forge!99
2018-01-14 16:49:32 +00:00
austinio7116
f658ba29c9 Added token image for rekindling phoenix 2018-01-14 15:12:39 +00:00
Sol
57cf22af43 Merge branch 'newcard' into 'master'
Fixed a card

See merge request core-developers/forge!98
2018-01-14 14:48:51 +00:00
Sol
eaea379532 Merge branch 'upcoming_rix2' into 'master'
Ability description fix for Kumena

See merge request core-developers/forge!97
2018-01-14 13:36:33 +00:00
swordshine
2ac3829b6f - Wormfang Drake should not exile an opponent's creature 2018-01-14 18:07:14 +08:00
swordshine
29e3cf4a79 - Fixed the the plural merfolk in the typelist 2018-01-14 18:03:11 +08:00
maustin
d84e2e4e23 Merge remote-tracking branch 'origin/upcoming_rix2' into upcoming_rix2 2018-01-14 09:58:48 +00:00
austinio7116
02b8d39791 Ability description fix for Kumena 2018-01-14 09:58:32 +00:00
austinio7116
f7a74edc6e Ability description fix for Kumena 2018-01-14 09:50:31 +00:00
swordshine
8feaf0795f - Simplified the code 2018-01-14 10:53:21 +08:00
Sol
87a857220f Merge branch 'upcoming_rix2' into 'master'
Induced Amnesia

See merge request core-developers/forge!96
2018-01-14 01:48:59 +00:00
austinio7116
403550af5f Update ChooseCardEffect.java 2018-01-14 00:54:04 +00:00
maustin
d6ffdddfc6 Text correction "a creature" -> creature(s) for new WithTotalPower effect 2018-01-14 00:51:55 +00:00
austinio7116
7663df024c Added Induced Amnesia - cleaned up unneeded define 2018-01-14 00:32:17 +00:00
maustin
704c735bc5 Merge remote-tracking branch 'origin/upcoming_rix2' into upcoming_rix2 2018-01-14 00:21:21 +00:00
austinio7116
ec72e9f7d7 Added Induced Amnesia 2018-01-14 00:20:55 +00:00
austinio7116
f95ada602c Added Induced Amnesia 2018-01-14 00:19:04 +00:00
Hanmac
7c10272d03 Fix: added References to tilonallis_summoner 2018-01-13 22:17:36 +01:00
Hanmac
3c3c64a11a AtEOT: add AtEOTCondition & AtEOTDesc 2018-01-13 17:11:23 +01:00
Michael Kamensky
722c16e925 Merge branch 'rix-rename' into 'master'
Migrate RIX to appropriate card letter folders

See merge request core-developers/forge!95
2018-01-13 15:45:18 +00:00
Chris H
4a799cf45f Migrate RIX to appropriate card letter folders 2018-01-13 10:21:23 -05:00
Sol
16acf2d46c Merge branch 'rix-cards' into 'master'
Added Flood of Recollection [RIX]

See merge request core-developers/forge!91
2018-01-13 14:35:16 +00:00
Sol
413c14a1dc Merge branch 'RIX' into 'master'
Rix

See merge request core-developers/forge!94
2018-01-13 14:34:57 +00:00
swordshine
826f37e4b9 - Added Cherished Hatchling 2018-01-13 22:15:55 +08:00
swordshine
39654e498c - Updated more scripts 2018-01-13 21:56:05 +08:00
swordshine
e3432ced3c - More oracle updates 2018-01-13 21:25:20 +08:00
swordshine
917ae1daab - Updated the Dinosaur type for a few cards 2018-01-13 21:15:50 +08:00
Michael Kamensky
a09086bfc2 Merge branch 'RIX' into 'master'
Updated the chooseCard effect for Slaughter the Strong

See merge request core-developers/forge!93
2018-01-13 13:08:20 +00:00
swordshine
2ac38e6639 - one more update 2018-01-13 20:02:59 +08:00
swordshine
3dc54790cc - Updated the chooseCard effect for Slaughter the Strong 2018-01-13 19:50:14 +08:00
Agetian
ce780c5175 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-01-13 12:25:02 +03:00
Michael Kamensky
35a76b4a43 Merge branch 'RIX' into 'master'
- Added Slaughter the Strong

See merge request core-developers/forge!92
2018-01-13 09:22:02 +00:00
swordshine
999f990870 - Added Awakened Amalgam by Marek14 2018-01-13 16:39:37 +08:00
swordshine
e050a62b0c - Added Slaughter the Strong 2018-01-13 16:21:59 +08:00
Agetian
1cd278afcf - Added Flood of Recollection [RIX]. 2018-01-13 09:05:09 +03:00
Agetian
208089a5cd Merge remote-tracking branch 'upstream/master' 2018-01-13 08:16:02 +03:00
Michael Kamensky
0c66c256b3 Merge branch 'sort-deck-folders' into 'master'
Sort the folder list in the deck editor

See merge request core-developers/forge!88
2018-01-13 05:13:59 +00:00
Agetian
66e8154243 Merge remote-tracking branch 'upstream/master' 2018-01-13 08:04:48 +03:00
Michael Kamensky
ff4db64802 Merge branch 'patch-6' into 'master'
Patch for Issue #8, coded by @friarsol

See merge request core-developers/forge!86
2018-01-13 04:51:09 +00:00
Michael Kamensky
4c29790b6c Merge branch 'patch-5' into 'master'
Sort Conspiracy before Eldrazi

See merge request core-developers/forge!85
2018-01-13 04:50:30 +00:00
Sol
3efc4807cf Merge branch 'RIX' into 'master'
- Added cost for Timestream Navigator

See merge request core-developers/forge!90
2018-01-13 03:24:17 +00:00
swordshine
a3b543cdd2 - Removed the extra line in the script 2018-01-13 11:04:12 +08:00
swordshine
a12eedfa93 - Added cost for Timestream Navigator 2018-01-13 10:30:20 +08:00
Sol
a97125d733 Merge branch 'patch-7' into 'master'
Fix DECK_EDITION tooltip

See merge request core-developers/forge!89
2018-01-13 00:21:43 +00:00
Rob Schnautz
7b5d2fc053 Fix DECK_EDITION tooltip 2018-01-12 22:42:51 +00:00
Jamin W. Collins
0e587c5b3a Sort the folder list in the deck editor
Signed-off-by: Jamin W. Collins <jamin.collins@gmail.com>
2018-01-12 12:09:27 -08:00
Michael Kamensky
6ea9a07a3d Merge branch 'upcoming_rix2' into 'master'
Corrected issues in editions file and rankings for RIX

See merge request core-developers/forge!87
2018-01-12 18:32:24 +00:00
austinio7116
090a47b17f Corrected issues in editions file and rankings for RIX 2018-01-12 18:08:46 +00:00
Rob Schnautz
f79b326010 Patch for Issue #8, coded by @friarsol 2018-01-12 18:06:02 +00:00
Rob Schnautz
4c6aa708c1 Sort Conspiracy before Eldrazi 2018-01-12 17:51:14 +00:00
Agetian
7ac66a298b Merge branch 'schnautzr/forge-patch-4' 2018-01-12 19:57:48 +03:00
Rob Schnautz
9e1baa9521 Add more spaces (non-functional) 2018-01-12 16:43:23 +00:00
Michael Kamensky
c1870b4ef8 Merge branch 'patch-5' into 'master'
Fix slowness issue when removing cards from decks.

See merge request core-developers/forge!84
2018-01-12 16:17:03 +00:00
Rob Schnautz
a9b068c89e fixed those spaces. 2018-01-12 16:13:07 +00:00
Sol
dab7d5030e Merge branch 'rix-planeswalker-decks' into 'master'
- Added RIX planeswalker decks as quest mode precons.

See merge request core-developers/forge!82
2018-01-12 15:40:23 +00:00
Sol
120a314a96 Merge branch 'assorted-fixes' into 'master'
- Renamed a couple cards in the RIX definition file.

See merge request core-developers/forge!80
2018-01-12 14:35:58 +00:00
Rob Schnautz
bd4305dd13 Fix slowness issue when removing cards from decks. 2018-01-12 14:24:40 +00:00
Michael Kamensky
d8b7214a2b Merge branch 'RIX' into 'master'
- Updated Commune with Dinosaurs

See merge request core-developers/forge!83
2018-01-12 08:25:55 +00:00
swordshine
95122ffcf8 - Updated Commune with Dinosaurs 2018-01-12 16:24:04 +08:00
Agetian
3b1b0bc92f - Update Commune with Dinosaurs to work with any Dinosaur card instead of just Creature.Dinosaur. 2018-01-12 11:20:58 +03:00
Agetian
49f789c404 - Removed a couple empty lines. 2018-01-12 10:43:04 +03:00
Agetian
9cf447d422 - Added RIX planeswalker decks as quest mode precons. 2018-01-12 10:36:47 +03:00
Agetian
1dc10b57c6 - Surge of Memories -> Flood of Recollection 2018-01-12 09:58:33 +03:00
Michael Kamensky
3d11422191 Merge branch 'revert-72e78226' into 'master'
Revert "Merge branch 'patch-4' into 'master'"

See merge request core-developers/forge!81
2018-01-12 06:48:03 +00:00
Michael Kamensky
fa0fc14c25 Revert "Merge branch 'patch-4' into 'master'"
This reverts merge request !77
2018-01-12 06:46:57 +00:00
Agetian
22cdb7e721 Merge branch 'master' of git.cardforge.org:core-developers/forge into assorted-fixes 2018-01-12 09:32:07 +03:00
Agetian
d215657641 - Renamed Shrine Altisaur to Temple Altisaur in the definition file 2018-01-12 09:30:21 +03:00
Rob Schnautz
df33b9388f Update ColumnDef.java 2018-01-12 05:41:09 +00:00
Rob Schnautz
ac53f16098 Sort Conspiracies before Eldrazi 2018-01-12 05:39:12 +00:00
Sol
36d6342e77 Merge branch 'fix-reckless-rage-desc' into 'master'
Fix reckless rage description

See merge request core-developers/forge!78
2018-01-12 04:04:48 +00:00
Sol
72e7822616 Merge branch 'patch-4' into 'master'
Fix slowness when removing a card from a deck by only getting the key once instead of twice.

See merge request core-developers/forge!77
2018-01-12 04:04:29 +00:00
Chris H
ea8f990172 Fix reckless rage description 2018-01-11 23:02:19 -05:00
Rob Schnautz
8003ade509 Fix slowness when removing a card from a deck by only getting the key once instead of twice. 2018-01-12 03:51:36 +00:00
Sol
3795232b2c Merge branch 'assorted-fixes' into 'master'
- Fix capitalization in the name of The City's Blessing token.

See merge request core-developers/forge!74
2018-01-12 02:20:45 +00:00
Sol
dda26098f4 Merge branch 'upcoming_rix2' into 'master'
Fixed Zacama's IsCast Property

See merge request core-developers/forge!76
2018-01-12 02:19:29 +00:00
maustin
ae042d98f4 Merge remote-tracking branch 'origin/upcoming_rix2' into upcoming_rix2 2018-01-11 21:53:07 +00:00
austinio7116
649faa3371 Fixed Zacama's IsCast property 2018-01-11 21:52:35 +00:00
Michael Kamensky
cea0c3f628 Merge branch 'upcoming_rix2' into 'master'
Added Release to the Wind (RIX)

See merge request core-developers/forge!75
2018-01-11 20:25:06 +00:00
Michael Kamensky
dce2ebbafb Update release_to_the_wind.txt (spell description fix). 2018-01-11 20:24:07 +00:00
austinio7116
c80d703acf Added Release to the Wind (RIX) 2018-01-11 19:54:27 +00:00
Agetian
f0418b8d2a - Name the Blessing token as it is named on the physical card. 2018-01-11 18:42:27 +03:00
Agetian
f363ae0f1e - Fix capitalization in the name of The City's Blessing token. 2018-01-11 18:31:49 +03:00
Michael Kamensky
24d3801316 Merge branch 'upcoming_rix2' into 'master'
Added Tilonallis Summoning with Swordshine's fixes

See merge request core-developers/forge!73
2018-01-11 15:29:45 +00:00
Sol
2a3f2c6dbd Merge branch 'fix-cantequip-crash' into 'master'
- Fix a crash when trying to equip a creature with an equipment that has CantEquip (e.g. O-Naginata)

See merge request core-developers/forge!70
2018-01-11 14:54:21 +00:00
Michael Kamensky
318a6e2924 Merge branch 'RIX' into 'master'
- Fixed a few cards

See merge request core-developers/forge!72
2018-01-11 14:15:07 +00:00
austinio7116
800311fed8 Added Tilonallis Summoning with Swordshine's fixes 2018-01-11 14:05:17 +00:00
swordshine
e49d45bf4f - Update more scripts 2018-01-11 21:59:33 +08:00
swordshine
356bd9c635 - Fixed P/T of two cards 2018-01-11 21:07:53 +08:00
Michael Kamensky
b400ea6d33 Merge branch 'upcoming_rix2' into 'master'
Corrected creature type of Forefunner of the Heralds

See merge request core-developers/forge!71
2018-01-11 11:28:34 +00:00
austinio7116
7352f9094a Corrected creature type of Forefunner of the Heralds 2018-01-11 10:06:36 +00:00
Agetian
c90e8bd356 - CantEquip: modify the card scripts such that they use a colon delimiter. 2018-01-11 09:41:38 +03:00
Agetian
9f79073d14 - Fix a crash when trying to equip a creature with an equipment that has CantEquip (e.g. O-Naginata). 2018-01-11 09:11:46 +03:00
Sol
249f8a4673 Merge branch 'RIX' into 'master'
- Added The Immortal Sun

See merge request core-developers/forge!69
2018-01-11 04:04:08 +00:00
swordshine
fe8ec70159 - Added The Immortal Sun 2018-01-11 11:09:08 +08:00
Sol
1f5375fed2 Merge branch 'rix_edition_file' into 'master'
Fix RIX edition missing rarity

See merge request core-developers/forge!68
2018-01-11 02:09:12 +00:00
Chris H
804d47df7b Fix RIX edition missing rarity 2018-01-10 21:07:52 -05:00
Sol
c6dfc1ceb2 Merge branch 'upcoming_rix2' into 'master'
Added Azor, the Lawbringer (RIX)

See merge request core-developers/forge!67
2018-01-11 00:30:50 +00:00
austinio7116
b3727f6821 Update azor_the_lawbringer.txt 2018-01-10 23:06:45 +00:00
austinio7116
bc538da8ea Added Azor, the Lawbringer (RIX) 2018-01-10 22:53:30 +00:00
Agetian
465e25adff - Updating CHANGES.txt (some clarifications). 2018-01-10 19:50:09 +03:00
Agetian
809fc513ea Merge remote-tracking branch 'upstream/master' 2018-01-10 19:38:45 +03:00
Agetian
ea101baf0d - Updating CHANGES.txt. 2018-01-10 19:37:28 +03:00
Michael Kamensky
19f88d6e6d Merge branch 'RIX' into 'master'
Fixed a few cards and added Peace Talks

See merge request core-developers/forge!64
2018-01-10 15:17:59 +00:00
Michael Kamensky
7499d05bbc Merge branch 'master' into 'master'
Merge Marek's latest RIX efforts (Ascend)

See merge request core-developers/forge!65
2018-01-10 15:08:32 +00:00
Agetian
de2e3fc070 Merge branch 'Marek14/forge-master' 2018-01-10 18:03:36 +03:00
Agetian
9b85293349 - Fix a couple cards. 2018-01-10 17:48:33 +03:00
swordshine
bee34c064b - Removed a param in the previous commit 2018-01-10 22:09:26 +08:00
swordshine
7ba64ce136 - Added Peack Talks 2018-01-10 22:06:57 +08:00
swordshine
d80d2c5cda - Fixed Touch of Vitae 2018-01-10 20:12:17 +08:00
Marek Čtrnáct
2419b93682 Update expel_from_orazca.txt 2018-01-10 11:48:03 +00:00
Marek Čtrnáct
740b3f0c49 Update expel_from_orazca.txt 2018-01-10 11:47:35 +00:00
Marek Čtrnáct
0925379a6d Add new file 2018-01-10 11:45:54 +00:00
Marek Čtrnáct
0c89087579 Add new file 2018-01-10 11:40:39 +00:00
Marek Čtrnáct
0565082309 Add new file 2018-01-10 11:37:11 +00:00
Marek Čtrnáct
cf7fcb07a1 Update kumenas_awakening.txt 2018-01-10 11:32:18 +00:00
Marek Čtrnáct
ad99d21cdf Add new file 2018-01-10 11:31:33 +00:00
Marek Čtrnáct
3ad4da4f49 Add new file 2018-01-10 11:27:47 +00:00
Marek Čtrnáct
62238e0f81 Update deadeye_brawler.txt 2018-01-10 11:21:09 +00:00
Marek Čtrnáct
2928f157fa Update mausoleum_harpy.txt 2018-01-10 11:21:03 +00:00
Marek Čtrnáct
cacb64b2ca Update twilight_prophet.txt 2018-01-10 11:20:55 +00:00
Marek Čtrnáct
1f65c4723d Update resplendent_griffin.txt 2018-01-10 11:20:42 +00:00
Marek Čtrnáct
c2d4923706 Add new file 2018-01-10 10:20:51 +00:00
Marek Čtrnáct
5ea07389b4 Add new file 2018-01-10 10:16:01 +00:00
Marek Čtrnáct
8c56bb51ed Update slippery_scoundrel.txt 2018-01-10 10:15:11 +00:00
Marek Čtrnáct
7c8057dbc6 Add new file 2018-01-10 10:14:42 +00:00
Marek Čtrnáct
841b9ae668 Add new file 2018-01-10 10:13:13 +00:00
Marek Čtrnáct
f847a39eaa Add new file 2018-01-10 10:11:59 +00:00
Marek Čtrnáct
f2eafb99d4 Update dusk_charger.txt 2018-01-10 10:10:03 +00:00
Marek Čtrnáct
847becccfe Add new file 2018-01-10 10:09:54 +00:00
Marek Čtrnáct
536c2483b0 Add new file 2018-01-10 10:08:02 +00:00
Marek Čtrnáct
af80228bcd Add new file 2018-01-10 10:05:21 +00:00
Marek Čtrnáct
334b086efb Add new file 2018-01-10 09:49:22 +00:00
Marek Čtrnáct
bddce9872f Add new file 2018-01-10 09:41:15 +00:00
Marek Čtrnáct
3d4e544573 Add new file 2018-01-10 09:37:53 +00:00
Marek Čtrnáct
7fe68a1c4a Add new file 2018-01-10 09:34:28 +00:00
Marek Čtrnáct
8693b6ce81 Add new file 2018-01-10 09:25:18 +00:00
Marek Čtrnáct
200b3f7092 Add new file 2018-01-10 09:20:42 +00:00
Marek Čtrnáct
69afbde352 Add new file 2018-01-10 09:09:03 +00:00
Hanmac
052fb65518 CardFactoryUtil: add Ascend for non-permanent Spell 2018-01-10 07:30:03 +01:00
swordshine
c2ec6978c4 - Added a planeswalker subtype: Angrath 2018-01-10 14:21:41 +08:00
Michael Kamensky
941e25d7a7 Merge branch 'RIX' into 'master'
- Added Sphinx's Decree

See merge request core-developers/forge!62
2018-01-09 15:28:08 +00:00
swordshine
0bddb07ba0 - Added Sphinx's Decree 2018-01-09 22:11:45 +08:00
Michael Kamensky
869697352d Merge branch 'master' into 'master'
Master

See merge request core-developers/forge!59
2018-01-09 13:58:43 +00:00
Agetian
9757fdb16b Merge branch 'Marek14/forge-master'
# Conflicts:
#	forge-gui/res/cardsfolder/upcoming/canal_monitor.txt
#	forge-gui/res/cardsfolder/upcoming/daring_buccaneer.txt
#	forge-gui/res/cardsfolder/upcoming/goblin_trailblazer.txt
#	forge-gui/res/cardsfolder/upcoming/impale.txt
#	forge-gui/res/cardsfolder/upcoming/orazca_raptor.txt
#	forge-gui/res/cardsfolder/upcoming/sadistic_skymarcher.txt
#	forge-gui/res/cardsfolder/upcoming/siegehorn_ceratops.txt
#	forge-gui/res/cardsfolder/upcoming/vampire_revenant.txt
2018-01-09 16:56:27 +03:00
Agetian
4d718177b6 - Update and fix more cards (according to Swordshine's recommendations). 2018-01-09 16:50:55 +03:00
Marek Čtrnáct
2dff30f2cd Merge branch 'Marek14/forge-master' into 'master'
Updating and fixing some cards.

See merge request Marek14/forge!1
2018-01-09 08:00:30 +00:00
Agetian
8b5128510b - Update and fix more cards. 2018-01-09 10:29:26 +03:00
Agetian
1efa8d030b - Added PREY counter (e.g. Tetzimoc, Primal Death) 2018-01-09 10:25:45 +03:00
Agetian
c855a107ab - Experimental (needs review): grant SVars to tokens before granting abilities and triggers in order to ensure that the game can see SubAbility SVars (e.g. Rekindling Phoenix). 2018-01-09 10:17:44 +03:00
Agetian
a379279956 - Update and fix some cards. 2018-01-09 09:24:49 +03:00
Agetian
3fb38ae682 Merge branch 'master' of git.cardforge.org:Marek14/forge into Marek14/forge-master 2018-01-09 08:35:56 +03:00
Michael Kamensky
ee29d4289c Merge branch 'Fix-RepeatEach-Changing-Zones' into 'master'
RepeatSubAbility host isn't set correctly if source changes zone

See merge request core-developers/forge!61
2018-01-09 05:18:46 +00:00
Chris H
eeccc8490d RepeatSubAbility host isn't set correctly if source changes zone 2018-01-08 23:44:40 -05:00
Sol
2ae735db74 Merge branch 'PossibilityStormPuzzles' into 'master'
- Added Possibility Storm puzzles for Ixalan and Iconic Masters (by Xitax).

See merge request core-developers/forge!58
2018-01-09 04:38:26 +00:00
Sol
248d6c57c9 Merge branch 'RIX' into 'master'
Rix

See merge request core-developers/forge!60
2018-01-09 04:37:46 +00:00
swordshine
5eba9bfc14 - Removed debug info 2018-01-09 12:17:05 +08:00
swordshine
7bc14a526d - Added Azor's Gateway 2018-01-09 12:14:19 +08:00
Marek Čtrnáct
769521c978 Update arguels_blood_fast_temple_of_aclazotz.txt 2018-01-08 17:31:45 +00:00
Marek Čtrnáct
bc71ef6a18 Add new file 2018-01-08 17:31:34 +00:00
Marek Čtrnáct
422ab9b835 Delete Bombard 2018-01-08 17:24:31 +00:00
Marek Čtrnáct
410f9b1acf Update dire_fleet_hoarder.txt 2018-01-08 17:14:07 +00:00
Marek Čtrnáct
f6b3cb4b53 Add new file 2018-01-08 17:09:18 +00:00
Marek Čtrnáct
6a72aab106 Add new file 2018-01-08 16:29:05 +00:00
Marek Čtrnáct
f3695e18ac Add new file 2018-01-08 16:19:36 +00:00
Marek Čtrnáct
f64846c690 Add new file 2018-01-08 15:17:14 +00:00
Marek Čtrnáct
79bb27d2a7 Add new file 2018-01-08 14:01:52 +00:00
Agetian
f0dba0ca0e - Added Possibility Storm puzzles for Ixalan and Iconic Masters (by Xitax). 2018-01-08 16:09:10 +03:00
Marek Čtrnáct
b70627bd3f Update dire_fleet_hoarder.txt 2018-01-08 13:08:31 +00:00
Marek Čtrnáct
91b894958c Add new file 2018-01-08 12:37:47 +00:00
Marek Čtrnáct
ef10e83f1c Add new file 2018-01-08 11:37:02 +00:00
Marek Čtrnáct
e958cb665e Add new file 2018-01-08 10:56:09 +00:00
Marek Čtrnáct
8413ec92d5 Add new file 2018-01-08 10:50:45 +00:00
Marek Čtrnáct
92d77b610d Update tezzerets_betrayal.txt 2018-01-08 10:10:18 +00:00
Marek Čtrnáct
42f445b1d2 Update arguels_blood_fast_temple_of_aclazotz.txt 2018-01-08 08:42:06 +00:00
Marek Čtrnáct
ca886f4c0c Add new file 2018-01-08 08:21:46 +00:00
Marek Čtrnáct
531ac8fc50 Add new file 2018-01-08 08:05:19 +00:00
Marek Čtrnáct
628c8f5ff1 Update arguels_blood_fast_temple_of_aclazotz.txt 2018-01-08 07:57:08 +00:00
Agetian
b63037caf3 Merge branch 'swordshine/forge-master' 2018-01-08 10:48:21 +03:00
Marek Čtrnáct
19127cb09d Add new file 2018-01-08 07:46:28 +00:00
Marek Čtrnáct
004e0a4265 Add new file 2018-01-08 07:45:52 +00:00
Marek Čtrnáct
9bbc23d550 Update safe_haven.txt 2018-01-08 07:33:49 +00:00
Marek Čtrnáct
77cea9f74e Add new file 2018-01-08 07:29:34 +00:00
Marek Čtrnáct
0bc57c145a Update military_intelligence.txt 2018-01-08 07:28:42 +00:00
swordshine
92545309e1 - Added Blood Sun 2018-01-08 13:01:16 +08:00
Sol
bdc2fe8e69 Merge branch 'master' into 'master'
- fixed a few cards

See merge request core-developers/forge!56
2018-01-08 04:22:33 +00:00
swordshine
1d3c5c85ec - fixed a few cards 2018-01-08 11:53:44 +08:00
Marek Čtrnáct
cd14da0454 Add new file 2018-01-07 22:58:10 +00:00
Marek Čtrnáct
73ccaac656 Add new file 2018-01-07 22:19:02 +00:00
Marek Čtrnáct
b19df1be9f Update tamiyo_the_moon_sage.txt 2018-01-07 22:11:17 +00:00
Marek Čtrnáct
f10bfe8e8d Add new file 2018-01-07 22:06:53 +00:00
Marek Čtrnáct
3418fdf822 Update dire_fleet_neckbreaker.txt 2018-01-07 22:01:23 +00:00
Marek Čtrnáct
f31098bf15 Add new file 2018-01-07 21:59:39 +00:00
Marek Čtrnáct
27ec3db33d Update ajanis_comrade.txt 2018-01-07 21:57:52 +00:00
Marek Čtrnáct
8b618df712 Update snapping_sailback.txt 2018-01-07 21:44:17 +00:00
Marek Čtrnáct
a87d428563 Update dire_fleet_neckbreaker.txt 2018-01-07 21:43:14 +00:00
Marek Čtrnáct
d778138f92 Add new file 2018-01-07 21:40:07 +00:00
Marek Čtrnáct
1197b1c59a Add new file 2018-01-07 21:37:17 +00:00
Marek Čtrnáct
e5cf4e8cea Add new file 2018-01-07 20:08:13 +00:00
Hanmac
4bbfb1852f CardFactoryUtil: add Ascend Trigger for Permanent and example 2018-01-07 20:46:22 +01:00
Marek Čtrnáct
d88ecc8505 Add new file 2018-01-07 19:18:27 +00:00
Marek Čtrnáct
6d1ef8257f Add new file 2018-01-07 19:13:48 +00:00
Marek Čtrnáct
5167eabb5c Add new file 2018-01-07 19:08:38 +00:00
Marek Čtrnáct
c8f08f61e5 Add new file 2018-01-07 19:01:25 +00:00
Marek Čtrnáct
9ed4822a93 Add new file 2018-01-07 18:58:30 +00:00
Marek Čtrnáct
0db2fba9fa Add new file 2018-01-07 18:53:17 +00:00
Marek Čtrnáct
117ee3d7bc Add new file 2018-01-07 18:43:42 +00:00
Marek Čtrnáct
c30261f9b8 Add new file 2018-01-07 18:42:46 +00:00
Marek Čtrnáct
5bd0fcf866 Update fall_of_the_hammer.txt 2018-01-07 18:23:04 +00:00
Hanmac
c888a09a40 AscendEffect: add Effect to add Blessing to Player 2018-01-07 18:56:52 +01:00
Marek Čtrnáct
c88818aa55 Add new file 2018-01-07 17:56:11 +00:00
Marek Čtrnáct
bafbdc8a5a Add new file 2018-01-07 17:44:10 +00:00
Marek Čtrnáct
a04c9f144a Update sun_crowned_hunters.txt 2018-01-07 17:42:06 +00:00
Marek Čtrnáct
3301d91ae7 Add new file 2018-01-07 17:37:00 +00:00
Marek Čtrnáct
6c4ed98ad4 Add new file 2018-01-07 17:29:53 +00:00
Marek Čtrnáct
273894b734 Add new file 2018-01-07 17:28:14 +00:00
Marek Čtrnáct
2fd3b13d86 Add new file 2018-01-07 17:22:41 +00:00
Marek Čtrnáct
e3ec921e3d Add new file 2018-01-07 17:18:00 +00:00
Marek Čtrnáct
8e4ce46891 Add new file 2018-01-07 17:16:20 +00:00
Marek Čtrnáct
9c50f45866 Add new file 2018-01-07 17:11:25 +00:00
Hanmac
2b3733a752 ExploreEffect: remove unused vars 2018-01-07 18:03:44 +01:00
Hanmac
ae709d210e Keyword: add Ascend to keywords 2018-01-07 18:03:08 +01:00
Hanmac
8f94bceaf2 CardTraitBase: make negative Conditions possible 2018-01-07 18:02:18 +01:00
Marek Čtrnáct
36fbb71e1d Add new file 2018-01-07 17:00:33 +00:00
Marek Čtrnáct
464a1fe51c Update impulsive_maneuvers.txt 2018-01-07 16:57:53 +00:00
Marek Čtrnáct
4067caa144 Add new file 2018-01-07 16:56:53 +00:00
Marek Čtrnáct
c576b0f65c Add new file 2018-01-07 16:46:09 +00:00
Marek Čtrnáct
6156ad254f Add new file 2018-01-07 16:42:04 +00:00
Marek Čtrnáct
3b0465aafe Add new file 2018-01-07 16:39:24 +00:00
Marek Čtrnáct
d7da7a3fd1 Add new file 2018-01-07 16:32:53 +00:00
Marek Čtrnáct
3fb515c73b Add new file 2018-01-07 16:29:53 +00:00
Marek Čtrnáct
8ef5fbc84b Add new file 2018-01-07 16:28:01 +00:00
Marek Čtrnáct
51f1c281e7 Add new file 2018-01-07 16:24:36 +00:00
Marek Čtrnáct
d93e9170ad Add new file 2018-01-07 16:17:13 +00:00
Michael Kamensky
418d4d5019 Merge branch 'upcoming_rix' into 'master'
Added Siegehorn Ceratops (RIX)

See merge request core-developers/forge!55
2018-01-07 16:14:31 +00:00
Michael Kamensky
bf6c569c01 Update siegehorn_ceratops.txt (em-dash) 2018-01-07 16:13:26 +00:00
Marek Čtrnáct
6e1a994971 Add new file 2018-01-07 16:10:22 +00:00
Marek Čtrnáct
0ed44d4afb Add new file 2018-01-07 16:09:01 +00:00
Marek Čtrnáct
92cc1c7718 Add new file 2018-01-07 16:03:52 +00:00
austinio7116
b4347ff53d Added Siegehorn Ceratops (RIX) 2018-01-07 15:54:41 +00:00
Marek Čtrnáct
3e2b4418ef Add new file 2018-01-07 15:53:31 +00:00
Michael Kamensky
691264c2c3 Merge branch 'upcoming_rix' into 'master'
Added Zacama, Primal Calamity (RIX)

See merge request core-developers/forge!54
2018-01-07 15:53:14 +00:00
Marek Čtrnáct
a21b2acf6f Add new file 2018-01-07 15:50:06 +00:00
Marek Čtrnáct
54a3052fdb Add new file 2018-01-07 15:42:16 +00:00
Marek Čtrnáct
33fdd2969a Add new file 2018-01-07 15:40:20 +00:00
Marek Čtrnáct
2f146c9648 Add new file 2018-01-07 15:37:12 +00:00
Marek Čtrnáct
d7ead4cc3d Add new file 2018-01-07 15:33:05 +00:00
Marek Čtrnáct
f5b275d0d1 Update forerunner_of_the_legion.txt 2018-01-07 15:30:18 +00:00
Marek Čtrnáct
5b18502378 Add new file 2018-01-07 15:28:06 +00:00
Marek Čtrnáct
792fa2e9ca Add new file 2018-01-07 15:21:31 +00:00
Marek Čtrnáct
c1f4400441 Add new file 2018-01-07 15:12:57 +00:00
austinio7116
18e813b3ed Added Zacama, Prinal Calamity (RIX) 2018-01-07 15:07:47 +00:00
Marek Čtrnáct
5bc66f4c76 Add new file 2018-01-07 15:05:57 +00:00
Marek Čtrnáct
df7d5e9c8e Update gonti_lord_of_luxury.txt 2018-01-07 15:00:44 +00:00
Marek Čtrnáct
a9c68d2942 Update gonti_lord_of_luxury.txt 2018-01-07 15:00:20 +00:00
Marek Čtrnáct
541afd807e Update gonti_lord_of_luxury.txt 2018-01-07 14:59:57 +00:00
Marek Čtrnáct
598a391c12 Add new file 2018-01-07 14:43:24 +00:00
Marek Čtrnáct
a9c536322e Add new file 2018-01-07 14:39:52 +00:00
Marek Čtrnáct
2635c1798d Add new file 2018-01-07 14:37:04 +00:00
Marek Čtrnáct
92f1596336 Update tasigurs_cruelty.txt 2018-01-07 14:33:17 +00:00
Marek Čtrnáct
9d41132be6 Add new file 2018-01-07 14:32:25 +00:00
Michael Kamensky
5c367ef940 Merge branch 'upcoming_rix' into 'master'
Polyraptor and Forerunner of the Heralds

See merge request core-developers/forge!53
2018-01-07 13:07:45 +00:00
Michael Kamensky
423d38c94e Update polyraptor.txt (em-dash) 2018-01-07 13:07:35 +00:00
Michael Kamensky
4ad20fd7d6 Update deeproot_elite.txt (select target description) 2018-01-07 13:06:48 +00:00
austinio7116
7cff83168f Added Polyraptor and Forerunner of the Heralds (RIX) 2018-01-07 07:21:54 +00:00
austinio7116
7ea109aaf8 Added Deeproot Elite and Jadelight Ranger (RIX) 2018-01-07 07:01:53 +00:00
Michael Kamensky
beb29502a7 Merge branch 'update-explore-sd' into 'master'
- Update ExploreEffect stack description (better implementation).

See merge request core-developers/forge!52
2018-01-07 05:47:29 +00:00
Agetian
1fed0a1264 - Update ExploreEffect stack description (better implementation). 2018-01-07 08:46:41 +03:00
Michael Kamensky
d2182ad49c Merge branch 'master' into 'master'
- Updated ExploreEffect to make the stack description account for cards that target other cards to explore.

See merge request core-developers/forge!44
2018-01-07 05:15:01 +00:00
Agetian
bb01e2a4d4 Merge branch 'Marek14/forge-master' 2018-01-07 08:10:01 +03:00
Agetian
b46c0bb05d - Several script corrections. 2018-01-07 08:08:42 +03:00
Agetian
192ef621f6 Merge branch 'master' of git.cardforge.org:Marek14/forge into Marek14/forge-master 2018-01-07 07:58:36 +03:00
Michael Kamensky
4a43abc905 Merge branch 'upcoming_rix' into 'master'
3 more green cards added

See merge request core-developers/forge!51
2018-01-07 04:52:13 +00:00
Michael Kamensky
07dfb2e9df Update cacophodon.txt (em-dash) 2018-01-07 04:47:12 +00:00
Agetian
2f8beff993 - Better implementation for ExploreEffect stack description. 2018-01-07 07:41:50 +03:00
Sol
72ec9a2b70 Merge branch 'update_ixalan_rankings' into 'master'
Update XLN rankings and add RIX rankings

See merge request core-developers/forge!46
2018-01-07 01:50:25 +00:00
austinio7116
1ce6979910 Added Cacophodon (RIX)
Added Crested Herdcaller
Added World Shaper
2018-01-06 23:43:54 +00:00
Marek Čtrnáct
c0794aff76 Add new file 2018-01-06 23:34:40 +00:00
Marek Čtrnáct
371ed047c6 Add new file 2018-01-06 23:29:47 +00:00
Marek Čtrnáct
b4cc04f32b Add new file 2018-01-06 23:19:35 +00:00
austinio7116
42596623a9 Added Cacophodon (RIX) 2018-01-06 23:15:40 +00:00
Marek Čtrnáct
345c809a23 Add new file 2018-01-06 23:12:36 +00:00
Marek Čtrnáct
fbe169f06d Add new file 2018-01-06 23:08:32 +00:00
Sol
4f5fc25195 Merge branch 'patch-2' into 'master'
Traveler's Amulet was missing.

See merge request core-developers/forge!48
2018-01-06 22:58:58 +00:00
Marek Čtrnáct
e1f3fb6ac0 Update snake_umbra.txt 2018-01-06 22:58:47 +00:00
Marek Čtrnáct
9915d3c9ac Update bident_of_thassa.txt 2018-01-06 22:57:26 +00:00
Sol
0bbed86241 Merge branch 'upcoming_rix' into 'master'
Preparing block data and draft rankings for RIX

See merge request core-developers/forge!47
2018-01-06 22:57:20 +00:00
Marek Čtrnáct
5076d03143 Add new file 2018-01-06 22:56:59 +00:00
Marek Čtrnáct
52b5ef7647 Update curious_obsession.txt 2018-01-06 22:53:03 +00:00
Marek Čtrnáct
e9f8877dec Add new file 2018-01-06 22:50:14 +00:00
Marek Čtrnáct
ed87267633 Add new file 2018-01-06 22:44:50 +00:00
Marek Čtrnáct
e4d469a408 Add new file 2018-01-06 22:42:08 +00:00
austinio7116
5c13e8d9aa Reverting rankings.txt update 2018-01-06 22:39:26 +00:00
austinio7116
7afdd4cbf5 Update formats.txt adding RIX to Standard and Modern 2018-01-06 22:38:19 +00:00
Marek Čtrnáct
c155317a6b Add new file 2018-01-06 22:36:59 +00:00
Marek Čtrnáct
a70513985a Add new file 2018-01-06 22:29:17 +00:00
Marek Čtrnáct
ffc2324dd5 Add new file 2018-01-06 22:24:06 +00:00
Rob Schnautz
9ef975d467 Traveler's Amulet was missing. 2018-01-06 21:51:38 +00:00
austinio7116
fa9c88c7be Preparing block data and draft rankings for RIX 2018-01-06 20:29:33 +00:00
Chris H
2d6b9b775e Update XLN rankings and add RIX rankings 2018-01-06 15:06:29 -05:00
Michael Kamensky
8a48371aec Merge branch 'patch-1' into 'master'
Update borderland_ranger.txt to E02 Oracle.

See merge request core-developers/forge!45
2018-01-06 19:13:57 +00:00
Rob Schnautz
c891a0fa26 Update borderland_ranger.txt to E02 Oracle. 2018-01-06 19:06:08 +00:00
Agetian
99728fd237 - Additional tweak for ExploreEffect stack description. 2018-01-06 21:56:37 +03:00
Agetian
b76c65441b Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-01-06 21:56:07 +03:00
Michael Kamensky
4ca8ec005d Merge branch 'patch-6' into 'master'
Update Evolving Wilds to IMA Oracle.

See merge request core-developers/forge!43
2018-01-06 18:54:17 +00:00
Michael Kamensky
db3c1b22ce Merge branch 'patch-5' into 'master'
Orazca Raptor (RIX)

See merge request core-developers/forge!42
2018-01-06 18:54:01 +00:00
Agetian
b0525bf06b - Additional tweak for ExploreEffect stack description. 2018-01-06 21:52:28 +03:00
Michael Kamensky
d884731691 Merge branch 'patch-3' into 'master'
Vampire Revenant (RIX)

See merge request core-developers/forge!40
2018-01-06 18:51:35 +00:00
Rob Schnautz
a4e4d089f7 Update Evolving Wilds to IMA Oracle. 2018-01-06 18:50:58 +00:00
Agetian
0b31ee8562 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-01-06 21:50:55 +03:00
Michael Kamensky
aaf8969af2 Merge branch 'patch-2' into 'master'
Sadistic Skymarcher (RIX)

See merge request core-developers/forge!39
2018-01-06 18:48:34 +00:00
Michael Kamensky
f7c34aa572 Merge branch 'patch-4' into 'master'
Goblin Trailblazer (RIX)

See merge request core-developers/forge!41
2018-01-06 18:47:05 +00:00
Agetian
16e4c11961 - Updated ExploreEffect to make the stack description account for cards that actually target other cards to explore (e.g. Enter the Unknown). 2018-01-06 21:46:12 +03:00
Rob Schnautz
116003f189 Orazca Raptor (RIX) 2018-01-06 18:45:22 +00:00
Michael Kamensky
6c87de30dd Merge branch 'upcoming_rix' into 'master'
Added Enter the Unknown (RIX)

See merge request core-developers/forge!37
2018-01-06 18:44:03 +00:00
Michael Kamensky
60b2601d8c Merge branch 'patch-1' into 'master'
Impale (RIX)

See merge request core-developers/forge!38
2018-01-06 18:42:02 +00:00
Rob Schnautz
ca37bda739 Goblin Trailblazer (RIX) 2018-01-06 18:41:42 +00:00
Rob Schnautz
c4698212ce Vampire Revenant (RIX) 2018-01-06 18:17:02 +00:00
Rob Schnautz
0e0d4031c9 Sadistic Skymarcher (RIX) 2018-01-06 17:54:36 +00:00
Rob Schnautz
be1f885341 Impale (RIX) 2018-01-06 17:22:30 +00:00
austinio7116
a6c8eedfd4 Added Enter the Unknown (RIX) 2018-01-06 17:17:02 +00:00
Michael Kamensky
32f9600d51 Merge branch 'patch-1' into 'master'
Canal Monitor (RIX)

See merge request core-developers/forge!36
2018-01-06 17:02:46 +00:00
Rob Schnautz
03f99c88ae Canal Monitor (RIX) 2018-01-06 16:47:28 +00:00
Michael Kamensky
47882191bc Merge branch 'patch-4' into 'master'
Sworn Guardian (RIX)

See merge request core-developers/forge!35
2018-01-06 16:44:23 +00:00
Michael Kamensky
d7962ae659 Merge branch 'patch-3' into 'master'
Soul of the Rapids (RIX)

See merge request core-developers/forge!34
2018-01-06 16:35:40 +00:00
Michael Kamensky
cbd17b0290 Merge branch 'upcoming_rix' into 'master'
Several green RIX cards

See merge request core-developers/forge!33
2018-01-06 16:32:22 +00:00
Rob Schnautz
839325ab70 Sworn Guardian (RIX) 2018-01-06 16:26:08 +00:00
Rob Schnautz
13dc4304fc Soul of the Rapids (RIX) 2018-01-06 16:20:02 +00:00
maustin
c49a937fb8 Added Swift Warden, Thrashing Brontodon (RIX) 2018-01-06 16:18:51 +00:00
maustin
36d3905d0e Merge remote-tracking branch 'origin/upcoming_rix' into upcoming_rix 2018-01-06 16:09:33 +00:00
austinio7116
9ff748dfd5 Added Orazca Frillback, Strength of the Pack, Thunderherd Migration (RIX) 2018-01-06 15:59:54 +00:00
austinio7116
40878794cf Added Jade Bearer, Jadecraft Artisan, Jungleborn Pioneer (RIX) 2018-01-06 15:43:16 +00:00
austinio7116
a17a69c93a Added Overgrown Armasaur (RIX) 2018-01-06 15:23:14 +00:00
austinio7116
469ca98b8a Added Knight of the Stampede (RIX) 2018-01-06 15:10:28 +00:00
Michael Kamensky
330cc1cd3a Merge branch 'master' into 'master'
Merging Marek's RIX upcoming cards so far.

See merge request core-developers/forge!32
2018-01-06 15:07:17 +00:00
Agetian
5c61819b25 Merge branch 'Marek14/forge-master' 2018-01-06 17:55:44 +03:00
Agetian
2c31c26cb9 - Fixing some card scripts. 2018-01-06 17:52:54 +03:00
Hanmac
a4f490b607 ExploreEffect: fixed for trigger and game state 2018-01-06 15:01:51 +01:00
Marek Čtrnáct
36007330e9 Add new file 2018-01-06 09:07:12 +00:00
Marek Čtrnáct
fba890af81 Add new file 2018-01-06 09:00:42 +00:00
Marek Čtrnáct
619fee0821 Update curious_obsession.txt 2018-01-06 08:50:31 +00:00
Marek Čtrnáct
4f7d3f4e4e Add new file 2018-01-06 08:45:47 +00:00
Marek Čtrnáct
ac1f1d2de4 Add new file 2018-01-06 08:43:35 +00:00
Marek Čtrnáct
5856edd962 Add new file 2018-01-06 08:36:17 +00:00
Marek Čtrnáct
ac463d081e Delete Sanguine Glorifier 2018-01-06 08:32:33 +00:00
Michael Kamensky
dbd1a872c4 Merge branch 'integrate-qw-theros' into 'master'
- Added Theros quest world by Xyx.

See merge request core-developers/forge!22
2018-01-06 08:32:27 +00:00
Marek Čtrnáct
ff600d652d Add new file 2018-01-06 08:32:26 +00:00
Michael Kamensky
8a512a13a4 Merge branch 'update-draft-cubes' into 'master'
Update draft cubes

See merge request core-developers/forge!25
2018-01-06 08:32:16 +00:00
Marek Čtrnáct
a9f1a25da4 Add new file 2018-01-06 08:26:21 +00:00
Marek Čtrnáct
0fd05f2c36 Add new file 2018-01-06 08:15:27 +00:00
Michael Kamensky
aa0754a53f Merge branch 'upcoming_rix' into 'master'
Giltgrove Stalker and Hardy Veteran

See merge request core-developers/forge!30
2018-01-06 08:15:01 +00:00
Marek Čtrnáct
d31d0aa735 Add new file 2018-01-06 08:10:20 +00:00
Marek Čtrnáct
c92b26928c Delete temple 2018-01-06 08:07:24 +00:00
Marek Čtrnáct
70f5f1be30 Add new file 2018-01-06 08:07:13 +00:00
Marek Čtrnáct
4ae769506e Add new file 2018-01-06 08:05:19 +00:00
Marek Čtrnáct
73c6b67830 Add new file 2018-01-06 08:01:46 +00:00
Marek Čtrnáct
f05963c6ea Add new file 2018-01-06 07:59:42 +00:00
Marek Čtrnáct
d99b687989 Add new file 2018-01-06 07:40:48 +00:00
austinio7116
d57e4bd077 Added Giltgrove Stalker, Hardy Veteran (RIX) 2018-01-06 07:37:06 +00:00
Marek Čtrnáct
e5c2a18bd6 Add new file 2018-01-06 07:22:31 +00:00
Marek Čtrnáct
796a2b44c8 Add new file 2018-01-06 07:20:48 +00:00
Marek Čtrnáct
5e67cf8292 Add new file 2018-01-06 07:18:35 +00:00
Rob Schnautz
5e4880f1d1 Update Rivals of Ixalan.txt 2018-01-06 07:02:44 +00:00
Rob Schnautz
ea161d83fa Exultant Skymarcher (RIX) 2018-01-06 07:02:44 +00:00
Rob Schnautz
0f28ca779d Everdawn Champion (RIX) 2018-01-06 07:02:44 +00:00
Marek Čtrnáct
7711af1214 Add new file 2018-01-06 06:48:10 +00:00
Agetian
9817eb303b Merge branch 'master' of git.cardforge.org:core-developers/forge into update-draft-cubes 2018-01-06 08:57:40 +03:00
Michael Kamensky
21d782b4be Merge branch 'patch-3' into 'master'
Everdawn Champion (RIX)

See merge request core-developers/forge!27
2018-01-06 05:11:10 +00:00
Michael Kamensky
f14d816e6b Merge branch 'upcoming_rix' into 'master'
added torridus's contributions from the forum

See merge request core-developers/forge!26
2018-01-06 05:11:01 +00:00
Michael Kamensky
025ee4df18 Merge branch 'patch-4' into 'master'
Exultant Skymarcher (RIX)

See merge request core-developers/forge!28
2018-01-06 05:10:58 +00:00
Sol
1b2870a1b2 Merge branch 'patch-5' into 'master'
Update Rivals of Ixalan.txt

See merge request core-developers/forge!29
2018-01-06 01:52:45 +00:00
Rob Schnautz
03760521ac Update Rivals of Ixalan.txt 2018-01-06 00:18:22 +00:00
Rob Schnautz
e4d8983280 Exultant Skymarcher (RIX) 2018-01-05 23:48:30 +00:00
Rob Schnautz
64431e3661 Everdawn Champion (RIX) 2018-01-05 23:41:52 +00:00
Marek Čtrnáct
ace56ece7e Add new file 2018-01-05 23:37:52 +00:00
Marek Čtrnáct
135724fab5 Update nesting_wurm.txt 2018-01-05 23:32:11 +00:00
Marek Čtrnáct
5d72b7ce98 Update myr_servitor.txt 2018-01-05 23:31:43 +00:00
Marek Čtrnáct
6697a44259 Update muscle_burst.txt 2018-01-05 23:31:16 +00:00
Marek Čtrnáct
85165850c1 Delete legion_conquistador.txt (found out it's a reprint from XLN) 2018-01-05 23:30:04 +00:00
Marek Čtrnáct
3bd24d80c0 Update kindle.txt 2018-01-05 23:28:54 +00:00
Marek Čtrnáct
aab31719d4 Update howling_wolf.txt 2018-01-05 23:28:33 +00:00
Marek Čtrnáct
1ffc84663a Update flame_burst.txt 2018-01-05 23:27:53 +00:00
Marek Čtrnáct
9f703cf64e Update feast_of_flesh.txt 2018-01-05 23:27:19 +00:00
Marek Čtrnáct
44b1e2a4e5 Update aether_burst.txt 2018-01-05 23:26:30 +00:00
Marek Čtrnáct
3e7fa3e440 Update accumulated_knowledge.txt 2018-01-05 23:25:55 +00:00
Marek Čtrnáct
20154d8411 Update squadron_hawk.txt -- ability should use "Squadron Hawk" instead of "CARDNAME" 2018-01-05 23:25:17 +00:00
Marek Čtrnáct
26b2a2278e Add new file 2018-01-05 23:23:59 +00:00
maustin
886901b48e Merge remote-tracking branch 'origin/upcoming_rix' into upcoming_rix 2018-01-05 19:52:13 +00:00
austinio7116
a817967aa0 Added torridus's contributions from the forum 2018-01-05 19:51:56 +00:00
Marek Čtrnáct
52bbfdb9f4 Imperial Ceratops 2018-01-05 19:13:30 +00:00
Marek Čtrnáct
8c74096a7f Forerunner of the Legion 2018-01-05 19:13:11 +00:00
Marek Čtrnáct
153362393b Famished Paladin 2018-01-05 19:12:46 +00:00
Marek Čtrnáct
3185d0b38b Exultant Skymarcher 2018-01-05 19:12:30 +00:00
Marek Čtrnáct
0be8b711ac Everdawn Champion 2018-01-05 19:12:09 +00:00
Marek Čtrnáct
9e60002210 Cleansing Ray 2018-01-05 19:11:55 +00:00
Marek Čtrnáct
fe169f267d Blazing Hope 2018-01-05 19:11:28 +00:00
Marek Čtrnáct
26c09c3993 Bishop of Binding 2018-01-05 19:11:12 +00:00
Marek Čtrnáct
49f516685f Baffling Eng 2018-01-05 19:10:49 +00:00
Agetian
89ca710932 - Updating CHANGES.txt. 2018-01-05 13:22:54 +03:00
Agetian
f1d01341cf - Update draft cubes ported from XMage. 2018-01-05 13:21:59 +03:00
Michael Kamensky
c30beda09d Merge branch 'upcoming_rix' into 'master'
3 new RIX cards

See merge request core-developers/forge!24
2018-01-05 05:09:08 +00:00
Michael Kamensky
4b4d9da7b1 Update elenda_the_dusk_rose.txt (removed an extra period in the description; changed the token name to Vampire). 2018-01-05 05:07:29 +00:00
maustin
32a94fbb37 Merge remote-tracking branch 'origin/upcoming_rix' into upcoming_rix 2018-01-04 20:27:40 +00:00
austinio7116
a589857582 Added Elenda, the Dusk Rose 2018-01-04 20:27:29 +00:00
maustin
f9c20c4a91 Merge remote-tracking branch 'origin/upcoming_rix' into upcoming_rix 2018-01-04 20:10:06 +00:00
austinio7116
886d07a4c3 Added Atzocan Seer (RIX) 2018-01-04 20:05:54 +00:00
maustin
b1ca421e52 Added Aquatic Incursion (RIX) 2018-01-04 19:53:24 +00:00
Michael Kamensky
1cb583a3ba Merge branch 'upcoming_rix' into 'master'
Added Swaggering Corsair (RIX)

See merge request core-developers/forge!23
2018-01-04 12:56:54 +00:00
Michael Kamensky
4683318351 Update swaggering_corsair.txt (removed First Strike in Oracle text, removed an empty whitespace in the Name tag). 2018-01-04 12:56:16 +00:00
austinio7116
e17a4756e7 Added Swaggering Corsair (RIX) 2018-01-04 10:07:08 +00:00
Agetian
7622f7c550 - Updating CHANGES.txt. 2018-01-04 09:31:30 +03:00
Hanmac
e25332ed27 fixed isBlessing in Variables 2018-01-04 07:30:50 +01:00
Hanmac
1f270a5231 Merge branch 'master' of git.cardforge.org:core-developers/forge 2018-01-04 07:18:06 +01:00
Hanmac
cc32286389 add Player Blessing 2018-01-04 07:17:31 +01:00
Agetian
d78b2f2fc0 - Added Theros quest world by Xyx. 2018-01-04 09:15:14 +03:00
austinio7116
a406bc94a8 Added Kumena 2018-01-03 23:20:34 +00:00
maustin
765a743d5b Merge branch 'master' of https://git.cardforge.org/core-developers/forge 2018-01-03 06:54:15 +00:00
Michael Kamensky
40a0d3a3e4 Merge branch 'patch-3' into 'master'
Zetalpa, Primal Dawn (RIX)

See merge request core-developers/forge!18
2018-01-03 06:52:31 +00:00
austinio7116
b612791fc8 Added Frilled Deathspitter 2018-01-03 06:52:06 +00:00
Michael Kamensky
c4cc51cf8e Merge branch 'patch-5' into 'master'
Angrath's Ambusher (RIX)

See merge request core-developers/forge!20
2018-01-03 06:52:01 +00:00
Michael Kamensky
3494d388bb Merge branch 'patch-6' into 'master'
Daring Buccaneer (RIX)

See merge request core-developers/forge!21
2018-01-03 06:51:40 +00:00
Michael Kamensky
88b887c579 Merge branch 'patch-4' into 'master'
Update Rivals of Ixalan.txt

See merge request core-developers/forge!19
2018-01-03 06:33:48 +00:00
Rob Schnautz
061f3ce78f Update Rivals of Ixalan.txt 2018-01-03 01:14:15 +00:00
Rob Schnautz
944d2475dd Daring Buccaneer (RIX) 2018-01-02 23:56:54 +00:00
Rob Schnautz
1d1e57a265 Angrath's Ambusher (RIX) 2018-01-02 23:37:57 +00:00
Rob Schnautz
522b874f54 Update Rivals of Ixalan.txt 2018-01-02 23:32:09 +00:00
Rob Schnautz
dea2cfad1c Zetalpa, Primal Dawn (RIX) 2018-01-02 22:53:35 +00:00
Michael Kamensky
1c8369d6fd Merge branch 'forge_icon' into 'master'
added 64 and 128 forge icon size to make forge icon smooth in Windows in default DPI

See merge request core-developers/forge!17
2018-01-02 12:02:13 +00:00
Nikolay Hidalgo Diaz
98d8d65687 added 64 and 128 forge icon size to make forge icon smooth in Windows in default DPI 2018-01-02 11:53:52 +03:00
Michael Kamensky
c5c9f5a447 Merge branch 'patch-3' into 'master'
Captain's Hook from RIX

See merge request core-developers/forge!9
2018-01-02 08:13:05 +00:00
Michael Kamensky
c42a3ac6e0 Merge branch 'patch-9' into 'master'
Vampire Champion (RIX)

See merge request core-developers/forge!15
2018-01-02 08:12:40 +00:00
Rob Schnautz
be60c4f865 Fix title. 2018-01-02 08:10:06 +00:00
Rob Schnautz
fd9bb6f36a Fix title. Not sure why it was truncated. 2018-01-02 08:08:51 +00:00
Michael Kamensky
27118a0f7a Merge branch 'patch-4' into 'master'
Brass's Bounty (RIX)

See merge request core-developers/forge!10
2018-01-02 07:00:40 +00:00
Michael Kamensky
665d4af5a2 Merge branch 'patch-5' into 'master'
Ghalta, Primal Hunger (RIX)

See merge request core-developers/forge!11
2018-01-02 07:00:35 +00:00
Michael Kamensky
1fd2aa17d5 Merge branch 'patch-7' into 'master'
Angrath, Minotaur Pirate (RIX)

See merge request core-developers/forge!13
2018-01-02 07:00:29 +00:00
Michael Kamensky
bd8db95098 Merge branch 'patch-8' into 'master'
Swab Goblin (RIX)

See merge request core-developers/forge!14
2018-01-02 07:00:22 +00:00
Michael Kamensky
fcb3f64d9b Merge branch 'patch-10' into 'master'
add Rivals of Ixalan editions file

See merge request core-developers/forge!16
2018-01-02 06:34:59 +00:00
Michael Kamensky
ee36ab94b6 Merge branch 'patch-6' into 'master'
Add upcoming to cardsfolder

See merge request core-developers/forge!12
2018-01-02 06:34:51 +00:00
Rob Schnautz
ac86437b35 add Rivals of Ixalan editions file 2018-01-02 04:17:28 +00:00
Rob Schnautz
30d8d9f3fe Vampire Champion (RIX) 2018-01-02 03:37:12 +00:00
Rob Schnautz
f2ace5cd3d Swab Goblin (RIX) 2018-01-02 03:25:11 +00:00
Rob Schnautz
cc1a71f246 Angrath, Minotaur Pirate (RIX) 2018-01-02 03:09:45 +00:00
Rob Schnautz
b3d8eaee7d Add new directory 2018-01-02 03:04:22 +00:00
Rob Schnautz
c70861d7d3 Ghalta, Primal Hunger (RIX) 2018-01-02 01:35:29 +00:00
Rob Schnautz
4cfa915aec Add new directory 2018-01-02 01:32:46 +00:00
Rob Schnautz
2bf31add95 Revert "Add new file"
This reverts commit 1efe09eeca
2018-01-02 01:31:22 +00:00
Rob Schnautz
61526dea82 Brass's Bounty (RIX) 2018-01-02 01:28:44 +00:00
Rob Schnautz
5c719c27b0 Add new directory 2018-01-02 01:28:17 +00:00
Rob Schnautz
342207375e Revert "Brass's Bounty (RIX)"
This reverts commit 2375176d72
2018-01-02 01:22:41 +00:00
Rob Schnautz
ce5b26b0d0 Captain's Hook (RIX) 2018-01-02 01:18:09 +00:00
Rob Schnautz
6056bfbc49 Revert "Captain's Hook from RIX"
This reverts commit 3bfc7da93a
2018-01-02 01:12:09 +00:00
Rob Schnautz
4e925b0385 Add new directory. Hopefully this is landing in the right place this time. 2018-01-02 01:04:15 +00:00
Rob Schnautz
edff4117f5 Revert "Add new directory"
This reverts commit 64f2d62605
2018-01-02 01:00:05 +00:00
Rob Schnautz
64f2d62605 Add new directory 2018-01-02 00:57:55 +00:00
Rob Schnautz
1efe09eeca Add new file 2018-01-02 00:42:32 +00:00
Rob Schnautz
2375176d72 Brass's Bounty (RIX) 2018-01-02 00:34:31 +00:00
Rob Schnautz
3bfc7da93a Captain's Hook from RIX 2018-01-02 00:21:25 +00:00
Michael Kamensky
34b4e90d75 Merge branch 'patch-2' into 'master'
Add Un-set basic lands and Steamflogger Boss

See merge request core-developers/forge!8
2018-01-01 16:56:44 +00:00
Rob Schnautz
48ab353521 Change "Expansion" to "Other" 2018-01-01 05:12:04 +00:00
Rob Schnautz
73f872bf6e Change "Expansion" to "Other" 2018-01-01 05:09:51 +00:00
Rob Schnautz
f04c8be999 Change "Expansion" to "Other" 2018-01-01 05:09:33 +00:00
Rob Schnautz
d6eb49db68 Add Steamflogger Boss and basic lands from Unstable 2017-12-31 23:47:44 +00:00
Rob Schnautz
c03ada1e1c Add basic lands from Unhinged 2017-12-31 23:45:22 +00:00
Rob Schnautz
9ce3d6182b Add basic lands from Unglued 2017-12-31 23:38:54 +00:00
Mitchell Matthews
ff3f60cc68 Merge branch 'patch-1' into 'master'
Detail the new Collector Number sort column in CHANGES.TXT

See merge request core-developers/forge!7
2017-12-30 08:26:46 +00:00
Rob Schnautz
89aa4bfab3 Detail the new Collector Number sort column in CHANGES.TXT 2017-12-30 06:34:09 +00:00
Hans Mackowiak
746e60b517 Merge branch 'master' into 'master'
Master

See merge request core-developers/forge!4
2017-12-30 06:09:21 +00:00
Michael Kamensky
8634cc561f Merge branch 'draft_import_deck' into 'master'
deck import in drafted deck editor

See merge request core-developers/forge!6
2017-12-30 05:58:25 +00:00
Michael Kamensky
d384f0896a Merge branch 'flabel_bicubic' into 'master'
Bicubic interpolation in FLabel

See merge request core-developers/forge!5
2017-12-30 05:51:48 +00:00
Nikolay Hidalgo Diaz
9f5cc344ad copy all sections from imported deck in constructed deck edtior 2017-12-30 08:11:20 +03:00
Nikolay Hidalgo Diaz
45bebdff0c deck import in drafted deck editor 2017-12-30 07:46:10 +03:00
Nikolay Hidalgo Diaz
59ef9e132a Bicubic interpolation in FLabel to keep player state icons smooth when resizing player panel 2017-12-30 06:54:53 +03:00
Rob Schnautz
af8e9c2466 Update SColumnUtil.java 2017-12-29 18:47:01 +00:00
schnautzr
e11163f483 Add ColumnDef COLLECTOR_ORDER to SColumnUtil 2017-12-29 12:44:14 -06:00
Sol
d3ff20484d Merge branch 'line-endings' into 'master'
Normalize line endings

See merge request core-developers/forge!2
2017-12-29 13:22:52 +00:00
Mitchell Matthews
76cbbb2bfe Merge branch 'test-git' into 'master'
- Updating CHANGES.txt and testing Git commit in the process.

See merge request core-developers/forge!3
2017-12-29 06:48:19 +00:00
Agetian
c51ad4aaa6 - Updating CHANGES.txt and testing Git commit in the process. 2017-12-29 09:34:11 +03:00
KrazyTheFox
6d5e56f6bf Normalize line endings 2017-12-28 23:40:29 -05:00
Agetian
251569e81c - Added two tutorial puzzles by Ikeda. 2017-12-27 06:52:02 +00:00
Hanmac
be2759c354 ColumnDef: updates for SortingName and Sorting CollectionOrder 2017-12-26 15:54:31 +00:00
Hanmac
69ee8f3630 update pom.xml with my info 2017-12-24 10:30:12 +00:00
Hanmac
610991a222 ManaCost: add Shard#isHybrid 2017-12-23 21:45:51 +00:00
Agetian
f08a0eaffd - Added a new Puzzle Mode game state flag, "NoETBTrigs", which allows you to ignore the ETB triggers and replacement effects for a card when necessary.
- Fixed a bug that made it tricky to add several precast flags to a puzzle due to absence of whitespace trimming.
- Added puzzles PC_012616 and PC_020216 by Xitax.
- Updated the puzzle PC_122915 to set itself up automatically without being obnoxious.
2017-12-21 06:11:21 +00:00
Agetian
a0baa659ec - Removed accidentally committed PC_012616 (will recommit later once an issue with it is addressed). 2017-12-20 04:27:24 +00:00
Agetian
536a19b373 - Added puzzles PC_022416, PC_021616, PC_021816 by Xitax. 2017-12-20 04:26:14 +00:00
Sol
27a2d7f642 Experimental fix for Attacking/Passing race condition 2017-12-19 03:03:35 +00:00
Agetian
fc29cec8c8 - Puzzle PS_HOU3: added some more Mountains to the human library to make it solvable using the Reddit solution. 2017-12-11 14:59:08 +00:00
Indigo Dragon
5fe19d75a6 Changed Blood Speaker from an awkward TrigSacrifice to a TrigSearch that uses Cost$ Sac.
Territorial Gorger; You get energy, not gain energy.
2017-12-11 10:24:38 +00:00
Sol
3252489971 Contains keyword should be checking for Contains, not startswith 2017-12-11 04:31:25 +00:00
Sol
f90af4784e Fix Signets being exploitable by not actually paying for activation 2017-12-10 20:30:48 +00:00
Hanmac
a2f11fb829 RE: do CARDNAME after text changing effects 2017-12-09 15:47:13 +00:00
Indigo Dragon
b6e6d308a9 Fixed some — issues (Non UTF-8). 2017-12-09 14:43:17 +00:00
Sol
dd04888106 RarityFilter from Luke 2017-12-09 02:38:50 +00:00
Agetian
0c397a414e - A better place for replacement effect CARDNAME conversion. 2017-12-08 10:35:54 +00:00
Agetian
2a62001048 - Fixed a NPE on mobile Forge when looking up certain cards with replacement effects (e.g. Kess, Dissident Mage). 2017-12-08 10:31:27 +00:00
Indigo Dragon
3132e5dd5b Removed reminder text from Mark of the Vampire.
Fixed Dread Summons cleanup.
2017-12-08 06:22:17 +00:00
Indigo Dragon
8843a12333 Added the Modern Event Deck, March of the Multitudes. 2017-12-07 12:52:00 +00:00
Indigo Dragon
b2e97ac73b Changes to rankings.txt
1. Removed Basic Lands
2. Changed AEther to Aether (and one Aerathi)
3. Fixed 5DN by proper capitalisation.
4. Added Ugin's Fate rankings. Rank is based upon original KTK/FRF rank.
5. Fixed Horizon Canopy (EXP), Death Pits of Rath, Seance, Noggle Hedge Mage, Scragnoth, Transguild Courier, Flame Kin Zealot, Evil Eye of Orms By Gore, Brush with Death, Will o' the Wisp, The Tabernacle at Pendrell Vale, Torsten Von Ursus, Abu Ja'far, Brutal Nightstalker.

Bug: Ironclaw Orcs is missing in 2ED, LEB, and LEA ranks. Another run through of those sets should be sufficient.
Bug Progress: Borrowing 100,000 Arrows and Guan Yu's 1,000-Li March. I discovered through these fixes that rankings.txt is case sensitive. That means the only two cards that begin with a number (" 1") must scare it somehow, meaning that these two cards cannot be assinged a ranking.
2017-12-07 12:48:19 +00:00
Indigo Dragon
11c440209a Updated Draft Rankings, including Planechase, Archenemy, Planechase 2, Planechase Anthology (A copy of Planechase 2), Archenemy: Nicol Bolas, Explorers of Ixalan.
Also fixed a few AE issues.
2017-12-07 06:40:36 +00:00
Indigo Dragon
400adeed1c Added Commander Draft Rankings. The ranking is based upon EDHREC Rank from Scryfall.
These are COM, C13, C14, C15, C16, CMA, C17.

As such, some low power cards undeservingly get high rankings eg Evolving Wilds, Diabolic Tutor, Herald's Horn.

To Do: Add Rankings to similar large supplementary sets eg Planechase, Archenemy

Bug: Borrowing 100,000 Arrows is not recognised in rankings.txt. As such, it always has a rank of 500.0.
2017-12-06 15:46:58 +00:00
Indigo Dragon
abcdc7253e 1. DeckHints Synergy between Trials and Cartouches.
2. Some Play Before Combats.
3. General Deck Hints.
2017-12-05 09:13:05 +00:00
Agetian
b6b8b9440f - Removed a useless line from the previous commit. 2017-12-05 08:57:13 +00:00
Agetian
c9a64b8e96 - Attempting to fix a bug with the replacement handler not converting CARDNAME. 2017-12-05 04:59:58 +00:00
Indigo Dragon
327795855a Reapplying 36129, except this time it works.
Turns out I accidentally changed the Name of liberating_combustion.txt, wrecking everything.
2017-12-04 07:58:52 +00:00
Agetian
b3c5494e74 - Reverted r36129 as requested. 2017-12-04 07:29:52 +00:00
Indigo Dragon
9fc4a247aa EDIT: WHOOPS THIS BORKED EVERYTHING PLS REVERT
Added plenty of DeckHints:Name$, some DeckHints:Color, many DeckNeeds:Name$
Most notably the Planeswalker Deck Into pack exclusive cards eg. [[Chandra, Pyrogenius]] and [[Liberating Combustion]]. New Policy: If a card would refer to another card, each get DeckHints:Name$ of the other.
2017-12-04 07:12:25 +00:00
Agetian
013cd0a9a8 - Added puzzle PC_122915 by Xitax (has some start-of-the-game quirks, documented in the description for now, may be addressed later). 2017-12-04 05:30:59 +00:00
Hanmac
05a9da4eb8 CardFactoryUtil: removed extra line for Suspend 2017-12-03 17:00:27 +00:00
Agetian
81d853ac04 - Preparing Forge for Mobile publish 1.6.5.001 [incremental]. 2017-12-03 15:36:57 +00:00
Blacksmith
af8dfa685a Clear out release files in preparation for next release 2017-12-03 15:27:30 +00:00
Blacksmith
946a360652 [maven-release-plugin] prepare for next development iteration 2017-12-03 15:21:13 +00:00
Blacksmith
071fe9a910 [maven-release-plugin] prepare release forge-1.6.5 2017-12-03 15:21:05 +00:00
Blacksmith
ccb4e87002 Update README.txt for release 2017-12-03 15:19:10 +00:00
austinio7116
cd9adb166a Updated frontier cube from cubetutor to include newer sets 2017-12-01 22:52:09 +00:00
Agetian
4f95f2c842 - Attempting to update the Java warning dialog to play ball with Java 9. 2017-11-30 05:35:31 +00:00
Agetian
7e6771d550 - [Part 2] Adding support for DamageDone replacement effects and triggers to work with emblems (for cards with ChooseSource). 2017-11-29 07:35:58 +00:00
Agetian
0b025fd0ae - [Part 1] Adding support for DamageDone replacement effects and triggers to work with emblems (for some cards, most other cards would need an extension to ChooseSource and possibly other changes). 2017-11-29 06:48:42 +00:00
Indigo Dragon
9445204ffc Fixed Magus of the Mind by adding the proper sacrifice cost (Seriously? Been abused for how long?) 2017-11-28 10:09:03 +00:00
Hanmac
0b69923ad7 StaticAbility: fixed References there being copied to the Static Ability 2017-11-23 14:05:26 +00:00
Hanmac
00eb90bcc6 CardFactory: remove copyCopiableAbilities because it isn't needed anymore 2017-11-22 14:15:30 +00:00
Indigo Dragon
8478967c04 Equipment Reminder Text changes. 2017-11-22 08:42:11 +00:00
Agetian
08f4b5bbe6 - Added Forge Puzzle Tutorial #01 by Ikeda. 2017-11-21 19:07:52 +00:00
Indigo Dragon
828a5e7f46 More Minor ' fixes. 2017-11-20 14:00:57 +00:00
Indigo Dragon
2e6151fe25 Added a Description to Arlinn Kord Emblem 2017-11-20 07:05:50 +00:00
Hanmac
c5fad910ba remove use of CardFactory.copyCopiableAbilities, it is already done by copyCopiableCharacteristics 2017-11-19 09:02:38 +00:00
Indigo Dragon
8d7c672b94 Minor Text Changes 2017-11-19 05:22:30 +00:00
Hanmac
67de9ba209 Card: do Suspend and Hideaway as better keywords 2017-11-17 07:15:56 +00:00
Hanmac
d3486341f3 Trigger: split replaceTriggerText into two different functions 2017-11-14 06:29:19 +00:00
Hanmac
ac12412ec3 AbilityFactory: use CardState instead of Card for the Svar params and the Names 2017-11-13 05:20:04 +00:00
austinio7116
73e0a4d35a Update to card-based deck generation model to include up to XLN PT 2017-11-12 19:53:43 +00:00
Sol
1e09e9e3f8 Fix hideaway not etb tapped 2017-11-10 16:05:06 +00:00
Agetian
d97265f2f2 - Fixed the intervening if clause on Garruk, the Veil Cursed -1. 2017-11-09 05:37:05 +00:00
Hanmac
cd55c4077e Keyword: make Suspend extra class to make the special case if it doesn't have Cost or amount 2017-11-09 05:23:28 +00:00
Indigo Dragon
0a9d5b73d8 Removed unneeded reminder text. 2017-11-07 04:47:05 +00:00
Indigo Dragon
e5e1de83a1 Added E02, Explorers of Ixalan. 2017-11-07 04:20:12 +00:00
Indigo Dragon
359ab4233b Added V17, Transform 2017-11-06 15:45:05 +00:00
Indigo Dragon
715f991d0d Added Experience Counters to Desktop Forge
Experience counters are at the lowest priority from Poison>Energy>Experience. It uses the Commander 2015 symbol, in which Experience Counters appeared.
There was a lot of transplanting involved copying the Energy code to fit the Experience model. This means there might be some unforseen problems though so check it out. I did several run throughs with Aetherwind Basker, Attune with Aether, Caress of Phyrexia, Leeches, and the Experience counter commanders to find anything remaining. There's a difference between "There are no bugs" and "I found no bugs".

Speaking of Leeches, there was an odd bug in the ProcessRunLogThing, saying "SVar 'X' not found in ability, fallback to Card (Leeches). Ability is ()". Something to do with Svar:X:TargetedPlayer$PoisonCounters maybe?
2017-11-06 15:15:21 +00:00
Indigo Dragon
e00b18bf40 Commander 2017 Oracle Update 2017-11-06 12:07:58 +00:00
Indigo Dragon
77869fd273 Disable some AI-Unusable artifacts. Check for AI changes.
Also disabled some parasitic XLN cards, cards that are completely useless without their tribe.
2017-11-06 11:55:45 +00:00
Indigo Dragon
1f4de4af80 Duel Decks Merfolk vs Goblins oracle updates, rarity changes, proper numbers 2017-11-06 10:46:47 +00:00
Agetian
6d34e04138 - Fixed the type line for Bloodmad Vampire. 2017-11-06 04:40:43 +00:00
Hanmac
1761ccffd0 Nahiri the Lithomancer: updated DeckHints and Stoneblade keywords 2017-11-04 11:26:17 +00:00
Hanmac
4de53b29f0 Bestow: if SpellAbilityRestriction do not copy if card is already a copy and bestowed
make SpellAbility clone check for the hostcard so when ActivationPlayer is set, it already knows about the new host
2017-11-04 10:28:09 +00:00
Hanmac
08c02b7473 TokenInfo: fixed that Token get extra Keyword abilities twice 2017-11-04 09:35:07 +00:00
Indigo Dragon
a648e888c5 Capitalisation 2017-11-03 07:10:50 +00:00
Agetian
af94789b7f - The cast in AbilityUtils.java:1038 is unsafe despite the comment saying otherwise. The game could crash under certain circumstances with a class cast exception (e.g. the AI attacking with Wings of the Guard into a planeswalker like Kaya, Ghost Assassin). 2017-11-03 06:40:52 +00:00
Hanmac
c4520a0ac2 CardPredicates: fixed containsKeyword 2017-11-02 05:38:16 +00:00
Hanmac
75bec0a132 AbilityManaPart: update SourceCard if it got updated inside of SpellAbility 2017-10-31 13:27:51 +00:00
Hanmac
b680878558 CardFactoryUtil: fixed multiple problems with Dredge 2017-10-30 19:25:50 +00:00
Hanmac
3f95ca2869 CardFactory: fixed etbCounter init twice for Planeswalker 2017-10-30 18:37:41 +00:00
Hanmac
003d7ac606 CardState & Keyword when copying the keywords and cardtraits, update the hostcard for them
for Keyword, update them when the Keyword get's copied
2017-10-30 10:17:18 +00:00
Hanmac
b61267d3bd CardFactoryUtil: hide ETB Tapped Replacement Effects, already got the keyword text 2017-10-28 07:42:48 +00:00
Hanmac
a6728853db CardFactoryUtil: fixed ETB tapped 2017-10-28 07:01:49 +00:00
Hanmac
d95c46e1b8 Keyword VII: Make KeywordInterface copyable for reasons
fixed CardFactoryUtil and etbCounter
2017-10-27 18:22:05 +00:00
Hanmac
d3a18c55d3 Try to fix Keyword Creation on CardState#copy 2017-10-27 06:16:08 +00:00
Hanmac
8bffadeb14 Keyword VI : the last of the changes 2017-10-27 02:45:12 +00:00
Hanmac
4abb037081 Keyword V: Rise of the intrinsic 2017-10-26 18:28:24 +00:00
Indigo Dragon
07d6cebba4 Fixed some mana costs. 2017-10-26 12:14:44 +00:00
Hanmac
abd929070e Keyword IV : a new Hope to finish 2017-10-25 20:31:42 +00:00
Hanmac
ea937a410b CloneEffect: fixed for Keyword change 2017-10-25 19:33:37 +00:00
Hanmac
64159bab6f Keyword Change III: now CardTraits aren't added to Card/CardState anymore but they are added to Keywords only
CardState uses update functions to fill the list from unhiddenKeywords
2017-10-25 19:09:36 +00:00
Agetian
164e6656e8 - Pump AILogic ContinuousBonus: don't target opponent's creatures. 2017-10-25 04:28:37 +00:00
Hanmac
0bac699cbf KeywordUpdate: fixed more stuff 2017-10-23 20:13:42 +00:00
Hanmac
2030ffcea3 KeywordApocalypse: return KeywordInstance whenever possible 2017-10-23 19:54:12 +00:00
Agetian
6433eff8ec - FIXME: Introducing a temporary measure to check for TargetedPlayerCtrl/TargetedPlayerOwn via the CastSA chain when the standard check fails. This can happen, for example, when an opponent's card is activated in hand (e.g. Chandra's Fury via Sen Triplets). Please assist, if possible, in investigating the source of this issue and fixing it (this may manifest itself in other places too, and most likely does). Feel free to revert this commit if you know a good solution. 2017-10-23 06:55:20 +00:00
Hanmac
6c51f7c6bc CardFactoryUtil: removed parseKeywords, and moved the last parts to methods
remove the need of Keyword.getInstance in each method
2017-10-22 18:04:36 +00:00
Hanmac
0c82e0614c Keyword Change: bind CardTraits onto Keyword(Instance/Interface) instead of KeywordsChange
call CardFactoryUtil functions only over Keyword(Instance/Interface)
2017-10-22 07:44:05 +00:00
Hanmac
acd30aaf08 Keyword: add KeywordInterface to be used instead of KeywordInstance<?> 2017-10-22 06:31:30 +00:00
Hanmac
ce2f75d43d KeywordCollection: refactor to make it work without string Map
KeywordInstance now has Original Keyword inside if needed elsewhere
Cycling better split into normal KeywordWithCost and TypeCycling into KeywordWithCostAndType
2017-10-21 19:20:45 +00:00
Hanmac
3f3875cc0c CardState: use KeywordCollection internal 2017-10-21 15:47:43 +00:00
Hanmac
e939218a65 KeywordCollection: use MultiMap to build it 2017-10-21 15:46:35 +00:00
Hanmac
d929a8b140 Koth & Elspeth: fixed EffectKeyword as Description 2017-10-21 15:38:14 +00:00
Hanmac
3e1aef4a58 CardFactoryUtil: shorted setupKeywordedAbilities and use the other functions 2017-10-20 06:16:14 +00:00
Hanmac
93dad0bf44 CostAdjustment: don't need Delve on SpellAbility 2017-10-20 06:09:58 +00:00
Agetian
d5259e8bfb - Added "CARDNAME can't phase out" to the list of non-stacking keywords (fixes AI using Ertai's Familiar). 2017-10-20 04:51:17 +00:00
Hanmac
440d7fe2b8 CardFactoryUtil: reworked ETB Replacement Keywords 2017-10-18 07:16:07 +00:00
Hanmac
0e666f07e9 CardFactoryUtil: moved Dredge to ReplacementEffect 2017-10-18 06:24:50 +00:00
Hanmac
1b542e936b CardTraitBase: moved sVars from SpellAbility 2017-10-18 06:16:23 +00:00
Hanmac
13487bbeed CardFactoryUtil: moved Amplify to ReplacementEffect 2017-10-18 05:43:34 +00:00
Hanmac
7a71716c7f cards: removed extra param from Champion 2017-10-17 05:38:27 +00:00
Hanmac
e72baa9dd4 CardFactoryUtil: moved Champion to addTrigger 2017-10-17 05:36:57 +00:00
Agetian
4c9f188b0c - A tweak to the previous method 2017-10-17 03:54:32 +00:00
Agetian
e78f90bc62 - Attempting to fix IndexOutOfBoundsError in CostRemoveAnyCounter. Couldn't trace it to the root of the problem yet though. 2017-10-17 03:53:44 +00:00
Hanmac
19d5e8457f Aura Barbs: use RelativeTarget 2017-10-16 17:58:10 +00:00
Hanmac
0fb0837561 DamageDealEffect: add RelativeTarget for to be relative to Damage Source 2017-10-16 17:54:57 +00:00
Hanmac
6cff21f47b AbilityUtils: add CardController for getDefinedPlayers 2017-10-16 17:52:05 +00:00
Hanmac
477408676d UntapAI: fixed non-final problems 2017-10-16 17:24:41 +00:00
Indigo Dragon
97b675bc9f THIRD RUN WITH DDT 2017-10-16 14:18:59 +00:00
Indigo Dragon
65dab56ad0 Second run with DDT 2017-10-16 14:18:10 +00:00
Indigo Dragon
faba97a6f5 First Run at DDT, based on leaked lists. 2017-10-16 14:11:21 +00:00
Agetian
c10a2e1133 - Minor improvement in Volrath's Shapeshifter AI logic. 2017-10-16 08:33:37 +00:00
Agetian
83d13cc153 - Fixed Wormfang Drake (does not use the Champion keyword). 2017-10-16 06:43:51 +00:00
Agetian
bdbdd990a7 - UntapAi: use the Tap to Untap Land logic for permanents only for now, to be improved later for sorceries. 2017-10-16 06:37:32 +00:00
Agetian
70e4d80965 - A more advanced untap logic for cards like Voyaging Satyr that allows the AI to pool mana and untap lands for more in order to cast a bigger spell. 2017-10-16 06:21:29 +00:00
Agetian
6ec8b15a34 - Added some AI logic flags to Deathrite Shaman. 2017-10-15 15:55:26 +00:00
Agetian
ee21afee61 - Preparing Forge for Android publish 1.6.4.003 [incremental]. 2017-10-15 15:43:30 +00:00
Agetian
08a08855aa - One more tweak to the previous commit. 2017-10-15 14:46:19 +00:00
Agetian
95ee7ba2f8 - Further logic update for Voyaging Satyr and similar cards. 2017-10-15 14:45:19 +00:00
Agetian
cde55f37f3 - Somewhat less risky logic for Voyaging Satyr and friends. 2017-10-15 14:40:40 +00:00
Agetian
5b147d4ab2 - Marking more cards with {T}: Untap target land as AI-playable (similar to Voyaging Satyr). 2017-10-15 14:25:46 +00:00
Agetian
058ef0c4c5 - Marking Arbor Elf as AI-playable (similar to Voyaging Satyr which is marked as such). 2017-10-15 14:18:08 +00:00
Agetian
bb48bd22a3 - Slight further improvement to PlayAi for Hideaway (might also consider improving for untargeted sorceries or sorceries with available legal targets, but that's iffy). 2017-10-15 14:05:43 +00:00
Agetian
0aecd7068f - Documenting changes in CHANGES.txt. 2017-10-15 13:39:57 +00:00
Agetian
0d4a837be6 - Patched up PlayAi to enable the AI to play Hideaway lands. Marking the relevant cards as playable.
- Some improvement to Volrath's Shapeshifter AI.
2017-10-15 13:38:17 +00:00
Agetian
ac70e6504a - Fixed the Ability$Counters deck hint in Proliferate. 2017-10-15 09:56:36 +00:00
Hanmac
a64f19a686 cards: update Reinforce cards 2017-10-15 08:27:27 +00:00
Agetian
9a12f8ecab - Fixed targeting for One with the Wind. 2017-10-15 08:18:04 +00:00
Agetian
37d6e5aa3d - Preparing Forge for Android publish 1.6.4.002 [incremental]. 2017-10-15 08:12:45 +00:00
Agetian
7cbf5ff74e - Added an implementation comment. 2017-10-15 08:11:05 +00:00
Hanmac
b6ec9728d8 CardFactoryUtil: add Reinforce as Keyword 2017-10-15 08:10:44 +00:00
Agetian
b5a167df05 - Unify most AITgts filtering calls in the AI code (except a couple which work differently). 2017-10-15 08:09:23 +00:00
Agetian
f01409ab2c - Documenting changes in CHANGES.txt. 2017-10-14 15:36:38 +00:00
Agetian
a96778a94d - Integrating Lorwyn quest world by Erazmus. 2017-10-14 15:34:11 +00:00
Agetian
e9e898da79 - Fix empty lines in scripts: part 3 2017-10-14 14:30:28 +00:00
Agetian
a8d73f6172 - Fix empty lines in scripts: part 2 2017-10-14 14:29:23 +00:00
Agetian
84a1d9997b - Fix empty lines in scripts: part 1 2017-10-14 14:17:43 +00:00
Hanmac
5aa489d769 Quicksilver Gargantuan: replaced ChooseCard with internal Choices for Clone 2017-10-14 11:31:43 +00:00
Agetian
fd520f2ea8 - Fix zero cost AB/DB on triggers: letters W through Z 2017-10-14 11:26:47 +00:00
Agetian
a1a7cb747b - Fix zero cost AB/DB on triggers: letters U and V 2017-10-14 11:25:49 +00:00
Agetian
3d79e77f47 - Fix zero cost AB/DB on triggers: letter T 2017-10-14 11:24:18 +00:00
Agetian
f6c6ecd494 - Fix zero cost AB/DB on triggers: letter S 2017-10-14 11:23:05 +00:00
Agetian
52cca9532b - Fix zero cost AB/DB on triggers: letters Q and R 2017-10-14 11:19:28 +00:00
Agetian
d412bcc718 - Fix zero cost AB/DB on triggers: letter P 2017-10-14 11:18:27 +00:00
Agetian
6cb93e51fa - Fix zero cost AB/DB on triggers: letters N and O 2017-10-14 11:17:12 +00:00
Hanmac
84bc9205a9 Gigantoplasm: replaced ChooseCard with internal Choices for Clone 2017-10-14 11:16:38 +00:00
Agetian
92aea19abd - Fix zero cost AB/DB on triggers: letter M 2017-10-14 11:16:08 +00:00
Agetian
5dc94fdf0e - Fix zero cost AB/DB on triggers: letter L 2017-10-14 11:15:04 +00:00
Agetian
2089450a1d - Fix zero cost AB/DB on triggers: letters J and K 2017-10-14 11:14:23 +00:00
Agetian
eb92fe944c - Fix zero cost AB/DB on triggers: letter I 2017-10-14 11:13:28 +00:00
Agetian
3f426f63a1 - Fix zero cost AB/DB on triggers: letter H 2017-10-14 11:12:51 +00:00
Agetian
8c3a96db7f - Fix zero cost AB/DB on triggers: letter G 2017-10-14 11:12:00 +00:00
Agetian
edd9142f64 - Fix zero cost AB/DB on triggers: letter F 2017-10-14 11:10:45 +00:00
Agetian
424b83ddeb - Fix zero cost AB/DB on triggers: letter E 2017-10-14 11:10:02 +00:00
Agetian
610e844747 - Fix zero cost AB/DB on triggers: letter D 2017-10-14 11:09:19 +00:00
Agetian
a6fbe153b5 - Fix zero cost AB/DB on triggers: letter C 2017-10-14 11:08:15 +00:00
Agetian
0c1108b92d - Fix zero cost AB/DB on triggers: letter B 2017-10-14 11:06:47 +00:00
Agetian
684b84690c - An inadvertent commit to forge-gui-mobile-dev: revert 2017-10-14 11:05:36 +00:00
Agetian
df808f5690 - Fix zero cost AB/DB on triggers: letter A 2017-10-14 11:04:04 +00:00
Agetian
d960623dac - Do not count unreachable planes in Conquered Events statistics for all planes. 2017-10-14 06:15:14 +00:00
Agetian
d30c95c58c - Fixed Phthisis. 2017-10-14 04:51:15 +00:00
austinio7116
8c1139c151 Update to card-based deck generator data to include XLN 2017-10-13 20:56:26 +00:00
Agetian
3a6adc5503 - Marked Vindictive Lich as RemAIDeck for the time being. 2017-10-13 09:47:32 +00:00
Agetian
dbc1556381 - Documenting changes in CHANGES.txt. 2017-10-13 07:49:27 +00:00
Agetian
bd85ed3577 - RemAIDeck update: deck hints for Proliferate and Sunburst. 2017-10-13 07:44:51 +00:00
Agetian
1340e43f02 - RemAIDeck update: next iteration (letter R, the rest). 2017-10-13 07:33:41 +00:00
Agetian
aee891cd7e - Updated AI flags for Ixalli's Keeper. 2017-10-12 20:24:07 +00:00
Agetian
9f39c1d305 - RemAIDeck update: next iteration (letter R, part 1). 2017-10-12 14:18:45 +00:00
Agetian
43c23059ba - RemAIDeck update: next iteration (letter J). 2017-10-12 13:10:01 +00:00
Agetian
12c1caf54d - RemAIDeck update: next iteration (letter I). 2017-10-12 05:40:27 +00:00
Agetian
3848ba311e - RemAIDeck update: next iteration (letter O). 2017-10-11 16:56:53 +00:00
Agetian
dcb6cbf5fa - AB$ Cost 0 -> DB$ in Contagion Engine. 2017-10-11 15:17:48 +00:00
Agetian
f70e33a3ea - CostAdjustment: account for cases when subability uses targeting and the parent SA doesn't (e.g. Spellwild Ouphe targeted with an AF Charm instant). 2017-10-11 15:00:54 +00:00
Hanmac
857d6f87a0 CardFactoryUtil: moved Suspend to addSpellAbility 2017-10-11 06:20:51 +00:00
Hanmac
d3444621b9 CardFactoryUtil: move Fortify 2017-10-11 05:38:17 +00:00
Agetian
05bfc51a20 - RemAIDeck update: next iteration (the rest of letter A). 2017-10-11 05:33:46 +00:00
Agetian
851c593695 - Capitalization fix. 2017-10-10 14:49:33 +00:00
Agetian
8ed711ad34 - Added puzzles PS_XLN1, PS_XLN2, renamed the preview Ixalan possibility storm puzzles. 2017-10-10 14:48:47 +00:00
Agetian
e8eaf60c2c - RemAIDeck update: next iteration (half of the letter A). 2017-10-10 14:33:40 +00:00
Agetian
df480b67c0 - RemAIDeck update: next iteration (the rest of letter N). 2017-10-10 13:55:45 +00:00
Indigo Dragon
31e7316854 Fixed Tamiyo the moon Sage Emblem Image 2017-10-10 13:09:12 +00:00
Agetian
b183588046 - RemAIDeck update: next iteration (more letter N). 2017-10-10 05:07:47 +00:00
Agetian
2620769b19 - RemAIDeck update: next iteration (the rest of letter F). 2017-10-10 04:50:03 +00:00
Sol
9f4184974e Extract TokenInfo from CardFactory 2017-10-10 00:52:32 +00:00
Agetian
e586debb58 - RemAIDeck update: next iteration (more letter F). 2017-10-09 19:07:52 +00:00
Agetian
eccaf83e7d - Removed a superfluous check from Extraplanar Lens. 2017-10-09 15:57:54 +00:00
Agetian
a9a6227b8f - RemAIDeck update: next iteration (letter E, some F). 2017-10-09 15:20:19 +00:00
Agetian
f16251d046 - A bit more Extraplanar Lens logic. 2017-10-09 09:35:35 +00:00
Agetian
27c508caa0 - RemAIDeck update: next iteration (a couple E cards). 2017-10-09 06:12:47 +00:00
Agetian
aa10845ef0 - RemAIDeck update: next iteration (letter H). 2017-10-09 05:42:12 +00:00
Agetian
fe4c836cd2 - CostAdjustment: check subabilities for valid targets (fixes Skulduggery targeting opponent's Kopala). 2017-10-09 03:34:57 +00:00
Agetian
a34afb1a81 - Removed a comment that doesn't apply. 2017-10-08 14:18:00 +00:00
Agetian
9ee58451b5 - RemAIDeck update: next iteration (Last Rites, mostly for Reanimator). 2017-10-08 14:16:41 +00:00
Hanmac
faa8ca0b34 DelayedTriggerAI: use getAdditionalAbility if able 2017-10-08 13:38:38 +00:00
Agetian
e084f66354 - RemAIDeck update: next iteration (letter L finished, beginning of letter H). 2017-10-08 13:35:54 +00:00
Agetian
c68887f5f5 - RemAIDeck update: next iteration (Sunburst). 2017-10-08 12:33:19 +00:00
Agetian
43862a4b7b - Better check for Sunburst. 2017-10-08 10:55:19 +00:00
Agetian
036d672e7c - Better AI check for Clearwater Goblet. 2017-10-08 10:42:47 +00:00
Agetian
abbbae011f - Fixed Rebound for the AI. 2017-10-08 10:10:30 +00:00
Agetian
bbab6a0d68 - Minor typo fix. 2017-10-08 09:54:34 +00:00
Agetian
2e492dfc39 - RemAIDeck update: next iteration (letters Y and Z, some L). 2017-10-08 09:43:59 +00:00
Agetian
3d5606cc7b - AI: reset the paid hash for newly played abilities similar to how it's done for PlayerControllerHuman. 2017-10-08 08:51:25 +00:00
Agetian
553d059113 - RemAIDeck update: next iteration (letter C). 2017-10-08 06:58:55 +00:00
Agetian
990345cda0 - Fixed keyword text generation for Morph. 2017-10-07 19:40:47 +00:00
Agetian
1cbf075036 - RemAIDeck update: next iteration. 2017-10-07 14:50:51 +00:00
Agetian
6e518df398 - RemAIDeck update: next iteration (the rest of letter K). 2017-10-07 14:21:11 +00:00
Agetian
8e1af41938 - Fixed a potential concurrent modification error. 2017-10-07 13:52:48 +00:00
Hanmac
da2faaeff4 Card: add addIntrinsicKeywords and fixed it in CardState
use it for CardFactory too
2017-10-07 12:55:36 +00:00
Agetian
66af4ef831 - Fixed a logic error in the previous commit. 2017-10-07 05:13:47 +00:00
Agetian
cc0fdb302b - RemAIDeck update: next iteration. 2017-10-07 05:04:05 +00:00
Agetian
fd1f686469 - RemAIDeck update: next iteration ("when CARDNAME enters the battlefield, return a creature you control to its owner's hand"). 2017-10-06 17:16:17 +00:00
Agetian
a45a8a6011 - RemAIDeck update: next iteration (the rest of letter G + letter X). 2017-10-06 14:16:42 +00:00
Agetian
a40c362764 - Fixed getTriggeringAbility not finding triggering abilities in the middle of a subability chain, which can happen when e.g. a delayed trigger is generated in the middle of a chain (fixes Goblin Kites). 2017-10-06 12:44:55 +00:00
Agetian
b3c372a803 - Fixed Aftermath/Flashback as REs. 2017-10-06 10:48:48 +00:00
Hanmac
0ffe6051b6 Flashback and Aftermath as Replacement Effetcs 2017-10-06 06:39:03 +00:00
Agetian
3613cb704e - RemAIDeck update: next iteration. 2017-10-06 05:54:29 +00:00
Hanmac
0733f7a69a ReplaceMoved: add missing part for Buyback 2017-10-05 16:56:06 +00:00
Agetian
d514944657 - PlayerControllerAi: fall out of Tap/Untap preference in case an invalid preference was specified, to get to the default return value. 2017-10-05 16:21:22 +00:00
Agetian
5172aaafdb - RemAIDeck update: next iteration (letters P and Q, some playable Graft). 2017-10-05 13:05:36 +00:00
Agetian
d8ec34aa6f - RemAIDeck update: next iteration. 2017-10-05 06:35:39 +00:00
Agetian
0257acce1d - RemAIDeck update: next iteration (Ashnod's Battle Gear and Flowstone Armor) 2017-10-05 05:04:15 +00:00
Agetian
f7746a0da6 - RemAIDeck update: next iteration (cards that grant continuous bonuses until untapped). 2017-10-05 04:45:22 +00:00
Agetian
52605f6a4a - Simple logic for Tawnos's Weaponry and potentially other similar cards. 2017-10-05 04:26:11 +00:00
Agetian
d865a8ba0c - Some Ixalan card logic update. 2017-10-05 03:48:01 +00:00
Agetian
2869337f52 - Fixed Broodmate Dragon. 2017-10-05 03:36:20 +00:00
Agetian
083f28c89d - Fixed the generated stack description for Thieves' Auction. 2017-10-04 19:29:24 +00:00
Agetian
bd78dff68f - RemAIDeck update: next iteration (letter T). 2017-10-04 16:26:53 +00:00
Agetian
db291eb9c4 - Some improvements for the Aristocrats AI logic. 2017-10-04 14:58:06 +00:00
Agetian
f155244997 - A little logic correction for cases where the AIPreference list would have an override. 2017-10-04 12:13:55 +00:00
Agetian
fa8d2f0a25 - AIPreference: obey the order in which the items are listed. 2017-10-04 12:11:15 +00:00
Agetian
f3b0259884 - RemAIDeck update: next iteration (letter U). 2017-10-04 10:15:41 +00:00
Agetian
258a9c046e - Updated PlayMain1:ALWAYS to work for non-permanent spells. Updated Decree of Justice to avoid a different mechanism for the same purpose. 2017-10-04 06:28:00 +00:00
Agetian
97bda7a949 - Formatting fix. 2017-10-04 06:08:38 +00:00
Agetian
6b306860ec - RemAIDeck update: next iteration (the rest of letter D). 2017-10-04 06:07:59 +00:00
Agetian
697c41b0d0 - TriggerTapsForMana: check the root ability for the tap cost, fixes interaction between e.g. Nykthos, Shrine to Nyx and Burning Earth. 2017-10-04 03:52:30 +00:00
Agetian
ee8ec490d7 - Corrected Wasitora, Nekoru Queen token name according to the preferred naming scheme. 2017-10-03 17:27:43 +00:00
Agetian
c1674b568a - RemAIDeck update: next iteration. 2017-10-03 16:12:47 +00:00
Agetian
2c7d75996e - Marked Phyrexian Unlife as having a non-stacking effect. 2017-10-03 15:02:31 +00:00
Agetian
579c08a9e8 - RemAIDeck update: next iteration (finishing the letter M). 2017-10-03 14:13:08 +00:00
Agetian
2ba6e616af - RemAIDeck update: Medicine Runner. 2017-10-03 13:56:18 +00:00
Agetian
5d32321aa2 - RemAIDeck update: next iteration.
- An option not to hold land drops if there are no permanents in play, so there's little reason and extra confusion.
- Some improvements to CountersRemoveAi and PhasesAi.
2017-10-03 13:53:34 +00:00
swordshine
ccd50fa98b - Updated Dueling Grounds 2017-10-03 13:14:56 +00:00
Agetian
69c9cb1244 - Improved AI for Mimic Vat. 2017-10-03 12:32:03 +00:00
Agetian
35e1fca22d - Fixed the Oracle name of Tishana's Wayfinder further. 2017-10-03 12:25:47 +00:00
Hanmac
039dfb63ea Buyback as ReplacementEffect
Soulfire Grand Master as Effects without Keyword
2017-10-03 11:17:07 +00:00
Indigo Dragon
cf53d03104 Renamed Tishana's "Wayfarer" 2017-10-03 11:09:21 +00:00
Agetian
d4e72f07f9 - RemAIDeck update: next iteration. 2017-10-03 11:09:10 +00:00
Agetian
08a6af547c - Fixed a logic error. 2017-10-03 09:59:35 +00:00
Agetian
dd8a526a30 - Fixed compile. 2017-10-03 09:17:34 +00:00
Agetian
298a6b48ad - A more conservative logic for chump blocking stuff equipped with Swords of X and Y. 2017-10-03 09:17:02 +00:00
Agetian
08d305ebfa - RemAIDeck update: next iteration. 2017-10-03 05:31:14 +00:00
Agetian
e4f0463299 - RemAIDeck update: next iteration. 2017-10-02 19:11:32 +00:00
Agetian
b586182160 - RemAIDeck update: next iteration. 2017-10-02 16:54:18 +00:00
Agetian
4051da7ee1 - Minor typo correction in a couple method names. 2017-10-02 14:11:07 +00:00
Agetian
1705cc21b4 - RemAIDeck update (Voltaic Key) 2017-10-02 14:06:58 +00:00
Hanmac
4a3e0b244c SpellAbility: add hasAdditonalAbility to be used in DelayedTrigger 2017-10-02 13:50:53 +00:00
Agetian
3855fa3568 - Relic Crush RemAIDeck status update. 2017-10-02 13:43:19 +00:00
Hanmac
70b0e7d6c0 Rebound: make it a ReplacementEffect as it should be
check for Fizzle in ReplaceMoved, so it only works when it resolved from Stack
2017-10-02 13:42:14 +00:00
Agetian
e40f3cb34d - RemAIDeck update: next iteration. 2017-10-02 13:40:54 +00:00
Agetian
434deb92f8 - AB -> DB in Vesuvan Doppelganger. 2017-10-02 13:39:47 +00:00
Agetian
d962905799 - Fixed Vesuvan Doppelganger not staying blue when cloning (note that .addColor doesn't work for some reason from CloneEffect, even though a similar call works in AF Animate; no other cards currently use this and require additional colors in DB Clone, but if some ever do, then this code will require an update). 2017-10-02 13:39:26 +00:00
Agetian
bb10ce78a0 - RemAIDeck update: next iteration. 2017-10-02 09:36:41 +00:00
Agetian
0670847700 - RemAIDeck update: next iteration 2017-10-02 08:43:45 +00:00
Agetian
c539acb823 - RemAIDeck update: next iteration (Scry with no drawback) 2017-10-02 07:40:36 +00:00
Agetian
f233c2683e - RemAIDeck update: next iteration 2017-10-02 07:30:41 +00:00
Agetian
e5a7f5844c - RemAIDeck update: next iteration 2017-10-02 06:53:14 +00:00
Agetian
db1839c402 - A less invasive Thought Lash hack for the AI. 2017-10-02 04:50:46 +00:00
Agetian
24b54a0937 - Removed an empty line. 2017-10-01 18:10:44 +00:00
Agetian
c3f7640c4d - A [hacky] fix to avoid the AI cheating with Thought Lash (otherwise it keeps exiling 1 card to it, no matter how many age counters are on it, essentially by "exiling" (not) the same card multiple times). A better solution is needed, feel free to improve. 2017-10-01 18:10:12 +00:00
Agetian
dab74b7bba - Documenting changes in CHANGES.txt. 2017-10-01 17:18:45 +00:00
Agetian
c0e9bb223e - RemAIDeck update: Everflowing Chalice 2017-10-01 17:05:40 +00:00
Agetian
0630658d4d - Massive RemAIDeck update, next iteration. 2017-10-01 16:56:59 +00:00
Agetian
3094850579 - Massive RemAIDeck revision - next iteration. 2017-10-01 15:41:28 +00:00
Agetian
7b29e2e603 - RepeatAi: Do not miss mandatory activations even if the repeat subability is set up in some kind of a substandard way (e.g. Sphinx's Tutelage) 2017-10-01 14:02:35 +00:00
Agetian
53784e154d - Dev mode: do not ask about summoning sickness for creatures with inherent Haste. 2017-10-01 13:40:15 +00:00
Agetian
20e3bbf288 - More RemAIDeck reevaluations. 2017-10-01 11:13:29 +00:00
Agetian
43d016c288 - Hunt for obscure RemAIDecks, part 3. 2017-10-01 10:26:49 +00:00
Agetian
320cc45fc8 - Hunt for obscure RemAIDecks, part 2. 2017-10-01 09:25:34 +00:00
Agetian
a1831ab338 - Hunt for obscure RemAIDecks: removing RemAIDeck in cards that are currently AI playable. Part 1. 2017-10-01 08:16:29 +00:00
Agetian
6f57f56d1d - Ixalan Oracle update: attacks/blocks each combat if able [please revise if possible, a bit of a many-sided modification]. 2017-10-01 07:22:26 +00:00
Agetian
41062dfc47 - Ixalan oracle update: Behemoth Sledge, Carpet of Flowers, Celestial Dawn, Conspiracy, Drafna's Restoration, Thran Turbine, Titanic Ultimatum. 2017-10-01 06:47:59 +00:00
Agetian
e6628d4569 - Use the new concat method. 2017-10-01 06:18:16 +00:00
Agetian
7c389dbf8e - Dev mode: Add Card to Battlefield allows to specify whether a creature should have summoning sickness or not, Repeat Last Add Card honors that choice. 2017-10-01 05:56:24 +00:00
Agetian
651f7cc7cd - PumpAi: if a pumped creature will not attack anyway (even with the pumped keywords), do not consider keyword pumps. Fixes the AI being trigger-happy with Glorifier of Dusk when it doesn't attack. 2017-10-01 04:51:07 +00:00
Agetian
bd70662314 - Implemented simple AI for Spires of Orazca. 2017-10-01 04:24:42 +00:00
Agetian
9a45a84663 - Fixed the AI crewing an already animated vehicle. 2017-10-01 04:07:06 +00:00
Agetian
5b12d0e3f6 - Fixed Divine Intervention triggering from the wrong zones. 2017-10-01 03:57:52 +00:00
Agetian
d9e1121796 - Fixed Celestial Convergence triggering from the wrong zones. 2017-10-01 03:56:01 +00:00
Hanmac
b0f37ca217 ComuterUtilAbility: some tweak to find with checking api first before iterate 2017-09-30 14:06:13 +00:00
Agetian
7459f7a9fa - AI: Now knows how to instantly reequip Cranial Plating to an unblocked attacker who would deal lethal damage. 2017-09-30 13:50:26 +00:00
Agetian
3f3f2f2c18 - Refactored the tap/untap AI detection to avoid code duplication. 2017-09-30 11:37:12 +00:00
Agetian
42f2b0a8e3 - AI: Improvements to tap/untap logic in order to try not to target something twice for tapping/untapping. 2017-09-30 11:26:01 +00:00
Agetian
8326dc1dc7 - AI: Try not to target cards for tapping that are already targeted for tapping by something on the stack. 2017-09-30 10:05:55 +00:00
Agetian
cc68c5cfcb - Fixed AI logic declaration for Vanquisher's Banner. 2017-09-30 09:11:48 +00:00
Agetian
9e46cc2b0b - Further improvements to the experimental prioritize dangerous threats for removal logic, hooked it up to Pump AI targeting opposing creatures for CARDNAME can't attack. 2017-09-30 09:05:11 +00:00
Agetian
2fc827fcd7 - Hooked the experimental priority removal code to ChangeZoneAi for bounce/exile opponent stuff as well (disabled by default, in testing). 2017-09-30 07:13:50 +00:00
Agetian
8108bdc91b - AI: added an experimental option to actively prioritize destroying creatures that are not immediately blockable and thus pose a recurring threat (currently in testing, disabled in all default profiles). 2017-09-30 06:57:17 +00:00
Agetian
345f6fc2af - Fixed a logic error in AiBlockController. 2017-09-30 06:02:45 +00:00
Sol
af06cb5cdd Fixed Oracle 2017-09-30 02:36:47 +00:00
Hanmac
ba74b333cd GameAction: add params hash for changeZone game actions
add old functions as overload
MagicStack: add Fizzle and StackInfomation to the params when Card is removed from stack
2017-09-29 19:58:01 +00:00
Agetian
895d282cc8 - A somewhat more detailed AI evaluation modifier for Master of Waves, which is more fair (25 for +1/+1 on all elementals + 15 per each Elemental token which may potentially be debuffed or even destroyed by getting rid of Master of Waves). 2017-09-29 18:35:32 +00:00
Agetian
33831ee7cc - Lowering the eval mod for Master of Waves a little 2017-09-29 18:22:43 +00:00
Agetian
8aaea65a72 - Added an experimental way to add a card-specific creature evaluation modifier for the AI (adding to Master of Waves for testing). 2017-09-29 18:13:38 +00:00
Agetian
e20ba2c0a0 - Removing several previously commented lines, they are confirmed to be superfluous. 2017-09-29 16:51:25 +00:00
Agetian
e276950caa - Removing triggers CombatDamageDoneOnce/DealtCombatDamageOnce (no longer needed, superseded by DamageDoneOnce/DamageDealtOnce). 2017-09-29 16:40:40 +00:00
Agetian
a17663655b - Added an option to decrease the AI chance to randomly trade vs. an Embalm/Eternalize creature that will come back later.
- Enabling the option to hold land drops for main 2 from time to time when safe (tested by now, should work well).
2017-09-29 16:34:38 +00:00
Agetian
9bb5a69b56 - Preparing Forge for Android publish v1.6.4.001 [incremental/new release]. 2017-09-29 16:22:05 +00:00
Blacksmith
b9afd98189 Clear out release files in preparation for next release 2017-09-29 16:20:22 +00:00
Blacksmith
89da789a99 [maven-release-plugin] prepare for next development iteration 2017-09-29 16:14:05 +00:00
Blacksmith
f78f8006d6 [maven-release-plugin] prepare release forge-1.6.4 2017-09-29 16:13:56 +00:00
Blacksmith
46c79b4213 Update README.txt for release 2017-09-29 16:12:26 +00:00
Agetian
e4fb8c20ac - Added a missing reference to Nest of Scarabs. 2017-09-29 15:28:10 +00:00
Agetian
5a8762edb1 - Do not rotate the foil effect on the card if it has no picture (it's then drawn vertically). 2017-09-29 15:00:35 +00:00
Agetian
6285c76935 - Fixed Oracle text for Master of Waves. 2017-09-29 14:33:38 +00:00
Agetian
77942b632f - A better way to detect whether a player can look at the card for the purpose of identifying valid split rotation. 2017-09-29 11:33:19 +00:00
Agetian
29261c34d5 - Attempt to avoid spoiling that the face down card is a split card if it's face down and can't legally be seen by the player.
- Rotate Aftermath split cards correctly when they're in the graveyard.
2017-09-29 11:24:00 +00:00
Agetian
2152e5f731 - Do not try to rotate the foil for the non-current zoomed card since they're shown non-rotated and will break the foil effect. 2017-09-29 11:01:41 +00:00
Agetian
b659833f71 - Documenting changes in CHANGES.txt. 2017-09-29 10:57:26 +00:00
Agetian
f7d626d8c6 - Fixed split cards not being foiled correctly in all cases because the game assumed the foil effect to be stored on one of the halves instead of on the original card state.
- A more comprehensive fix for rotating split cards with foil in mobile Forge that does not break them in hand/graveyard/whatever. Also, turned it into an option since it doesn't look good in portrait mode, especially on smaller cellphones and in single card zoom mode.
2017-09-29 10:54:56 +00:00
Agetian
f1c8ace081 - Some updates for the PS_XLN* puzzles. 2017-09-29 08:30:27 +00:00
Agetian
655015d33b - Some updates for the PS_XLN* puzzles. 2017-09-29 08:29:22 +00:00
Agetian
ec24a92987 - Added a way to visually rotate split cards in card zoomer in Desktop Forge. 2017-09-29 08:20:51 +00:00
Agetian
04237c6118 - Updating draft rankings. 2017-09-29 07:49:18 +00:00
Agetian
86f13e05d2 - Formatting fix. 2017-09-29 05:34:51 +00:00
Agetian
9a6146e1ba - One more tweak. 2017-09-28 18:22:17 +00:00
Agetian
0a8ce34252 - A more generic implementation for the previous commit. 2017-09-28 18:21:48 +00:00
Agetian
18529f47e7 - Special case for Bone Dancer and ordering graveyards in "With Relevant Cards" mode. 2017-09-28 18:19:33 +00:00
Agetian
98215be0fc - A couple Raid description tweaks and fixes. 2017-09-28 18:10:38 +00:00
Agetian
f2698ef38d - etbCounter keyword unification in Sekki, Seasons' Guide 2017-09-28 17:36:31 +00:00
Agetian
215ee66a02 - Comment tweak. 2017-09-28 17:33:50 +00:00
Agetian
e055657d13 - Added a SpellApiToAi assignment for AF GameDrawn. 2017-09-28 17:31:47 +00:00
Agetian
ec7f47dbe7 - Added Celestial Convergence.
- Added new effect AF GameDrawn that creates an intentional draw situation by game effect.
- Divine Intervention now uses this effect.
2017-09-28 17:31:04 +00:00
Agetian
e3e7e4d26a - I guess Divine Intervention needs to be RemAIDeck, since the AI will slab it senselessly (if it's even possible for a senseless card like that) 2017-09-28 16:36:42 +00:00
Agetian
118d7d735a - Added Divine Intervention (another silly old card that no one is probably going to play with, but someone had to script it, I guess). 2017-09-28 16:35:22 +00:00
Agetian
d7a8534354 - Fixed Snapping Sailback. 2017-09-28 16:11:20 +00:00
Agetian
9fe86bf72a - Vampire Nocturnus Avatar: ensure that its static ability only starts working when the game actually begins. 2017-09-28 16:08:38 +00:00
Agetian
1d76f89428 - Added puzzles PS_XLN1 and PS_XLN2. 2017-09-28 15:54:40 +00:00
Agetian
fc26af5a89 - Fixed Gonti, Lord of Luxury generated effect description. 2017-09-28 15:43:58 +00:00
Agetian
7f85a8f2e1 - A shorter name for the option to allow ordering cards in graveyard in mobile Forge (due to visual space constraints). 2017-09-28 13:40:47 +00:00
Agetian
e3ff8029de - Preparing Forge for Android publish 1.6.3.003 [hotfix]. 2017-09-28 13:23:52 +00:00
Agetian
70f5bdd339 - A couple fixes for the Kamigawa quest world. 2017-09-28 06:36:18 +00:00
Agetian
2ecc36ab12 - AI should not be so reckless with triggered pumps that result in losing the card at end of turn (e.g. Hazoret's Favor) 2017-09-28 06:24:44 +00:00
Agetian
a4c14c6be1 - Some improvements to the Splice AI: do not reconsider the SA unless at least something was chosen for splice to save processing time; reset the targets on the main SA because it'll need to be retargeted anyway. 2017-09-28 04:41:38 +00:00
Agetian
6cbda005e8 - Fixed Vampire Nocturnus Avatar. 2017-09-28 04:19:29 +00:00
kevlahnota
8ea99648f0 Try to fix rotated split card with foil overlay 2017-09-28 01:52:09 +00:00
Agetian
58a5d9b0e1 - Attempting to fix Splice onto Arcane AI: when adding splice effects by the AI, actually reconsider the entire SA (with spliced subs) via the canPlay routine (and set targets while at it) before deciding whether to play it or not. Might not be optimal, but at least it seems to stop the AI from wasting splice cards and making them disappear from the game into the void. Improvements are welcome. 2017-09-27 18:53:25 +00:00
Agetian
b969f2eac6 - Fixed a NPE in DiscardEffect. 2017-09-27 17:02:23 +00:00
Agetian
5c295e9080 - Turned CheckCondition into a generic top-level AILogic, used it for both Repeating Barrage and Sasaya, Orochi Ascendant at the same time. 2017-09-27 15:02:36 +00:00
Agetian
64a6c3c5bb - Fixed the AI cheating with Repeated Barrage Raid ability. 2017-09-27 14:59:07 +00:00
Agetian
22cc4c635a - Minor fix in CHANGES.txt. 2017-09-27 14:55:03 +00:00
Agetian
b5b96f1155 - Fixed preference capitalization. 2017-09-27 14:41:36 +00:00
Agetian
06b887cd93 - A somewhat more fine-grained and less spoiler-y option to order graveyards, now with three states (Never / With Relevant Cards / Always). 2017-09-27 14:40:48 +00:00
Agetian
c6ef376d15 - Fixed generated description for Uba Mask. 2017-09-27 13:24:09 +00:00
Agetian
6ef249195e - Minor clarification in CHANGES.txt. 2017-09-27 08:09:32 +00:00
Agetian
f6e99cf748 - Preparing Forge for Android publish 1.6.3.002 [incremental/bug fixes]. 2017-09-27 08:05:13 +00:00
Agetian
6d39088777 - Added NeedsOrderedGraveyard to Alms and Death Spark. 2017-09-27 08:04:28 +00:00
Agetian
e5cb026608 - Bushido AI: attempt to avoid accounting for it twice when predicting P/T bonuses. 2017-09-27 07:39:18 +00:00
Agetian
ce9dbc2b5f - Restoring support for Extended for the time being, part 2 2017-09-27 06:36:53 +00:00
Agetian
bfef677e93 - Restored support for Extended format (removing it from blocks.txt breaks Quest Mode completely). 2017-09-27 06:35:42 +00:00
Agetian
0d18a1d19a - PlayerControllerAi: when playing with ordered graveyards and there's a Volrath's Shapeshifter in the game, try to place the best creature on top of the graveyard for the most value if Volrath's Shapeshifter hits the battlefield. 2017-09-27 06:20:25 +00:00
Agetian
91b3b7194d - Updated ISSUES.txt. 2017-09-27 04:26:42 +00:00
Agetian
306d515e7f - Documenting changes in CHANGES.txt. 2017-09-27 04:21:05 +00:00
Agetian
0a21e03eac - Added Bosium Strip (currently with an implementation similar to Kess, Dissident Mage, which doesn't interact correctly in corner cases where another card also lets you cast cards from graveyard but allows you not to exile them; need a better way to check "a card cast this way" (by checking that it was cast from an effect of a particular source card). Improvements in this area are welcome). 2017-09-27 04:20:49 +00:00
Agetian
e112704a63 - Fixed Nissa's Judgment. 2017-09-27 03:51:50 +00:00
Agetian
c0bbf107c6 - Fixed an occasionally broken orderMoveToZoneList (fixes Sensei's Divining Top). 2017-09-27 03:51:37 +00:00
Agetian
84a6876265 - Added an experimental option to allow ordering cards going to graveyard when playing with cards that care about graveyard order (Volrath's Shapeshifter and others). Disabled by default. 2017-09-26 19:40:44 +00:00
Agetian
add9ffe5d9 - Boros Charm: fixed an ability description. 2017-09-26 18:22:09 +00:00
Agetian
c71f0b7e39 - Deploy the Gatewatch: look at the cards even if there are no valid choices. 2017-09-26 15:11:26 +00:00
Agetian
e934071716 - Volrath's Shapeshifter: switched to a less aggressive update schedule (no update unless necessary), which fixes interaction with composite triggers that consist of several related parts (e.g. Undying, Persist).
- Volrath's Shapeshifter: QoL update: do not show the same text and discard ability twice if Volrath's Shapeshifter is attempting to copy the text of another Volrath's Shapeshifter that is on top of the graveyard.
2017-09-26 13:55:28 +00:00
Agetian
5693cddc3a - Some refactoring in AiBlockController related to random trades.
- Enabling random trades for favorable gang double and triple blocks.
2017-09-26 12:48:22 +00:00
Agetian
561d27be0a - Comment style fix. 2017-09-26 10:20:04 +00:00
Agetian
2e19a2a99c - Fixed Mindbreak Trap. 2017-09-26 10:15:52 +00:00
Agetian
4055e421bc - Minor formatting tweak. 2017-09-26 10:14:00 +00:00
Indigo Dragon
792255b676 Adding a Commander banned list a la Tiny Leaders (Literally a la. Involved many copying and pasting)
Adding a Commander Format, so that decks can now be checked in the original deck editor. Don't know how to implement highlander rules though, (Aside from the obviously stupid way of Restricting EVERY CARD EVER MADE).

Retiring the Extended Format. This one's dead Jim, dead Jim, dead Jim. It's as dead as Frontier. Maybe. Yes. If anyone complains they can just remove the \\s.
2017-09-26 08:46:36 +00:00
Hanmac
0ddb8d9644 ExploreEffect: fixed trigger and counter part 2017-09-26 05:22:00 +00:00
Agetian
5c29555ae7 - Fixed a typo. 2017-09-26 03:49:59 +00:00
Agetian
cf5e6bde9a - Documenting changes in CHANGES.txt. 2017-09-26 03:43:48 +00:00
Agetian
515ddbb28d - Removed one more portion of the leftover DamageDone|OnlyOnce code, I believe this is the last one. 2017-09-26 03:28:13 +00:00
Agetian
18720e3693 - Some additional NPE protection in applyPotentialAttackCloneTriggers. 2017-09-25 13:28:19 +00:00
Agetian
6b21664dff - Documenting changes in CHANGES.txt. 2017-09-25 13:17:40 +00:00
Agetian
ca92f90f6d - Integrating Personal Ratings patch by Seravy. 2017-09-25 13:14:23 +00:00
Agetian
8a1ab40f3c - AF Explore: apparently there's no need for a special SpellDescription on it when used as an Execute target for a trigger. 2017-09-25 13:06:56 +00:00
Agetian
f738822cce - A more appropriate solution for the manland animation AI problem. 2017-09-25 11:21:48 +00:00
Agetian
2ef900443b - Fizzle DamageDoneOnce for cards returning to battlefield from graveyard 2017-09-25 11:14:54 +00:00
Agetian
917c6b7c54 - Removed one more portion of the now-unused DamageDone|OnlyOnce code which would crash Forge. 2017-09-25 11:13:30 +00:00
Agetian
966db8af9f - Improved the animate manland AI such that it doesn't try to animate manlands that are already tapped. 2017-09-25 11:03:54 +00:00
Indigo Dragon
07b26f03a7 Fixed some broken Conquest Decks 2017-09-25 08:22:25 +00:00
Agetian
d685997040 - ExploreAi: honor the DoNotDiscardIfAble SVar. 2017-09-25 08:06:14 +00:00
Agetian
fce6807a3b - Simple AI support for Explore (feel free to expand). 2017-09-25 08:02:08 +00:00
Agetian
e91b428bd5 - Removed the now-unused Explore hack from AF Dig. 2017-09-25 07:31:54 +00:00
Agetian
6581466239 - Fixed trigger name in Brazen Buccaneers. 2017-09-25 07:29:36 +00:00
Agetian
380a5bbadd - Script update: AF Explore (better AI support coming soon). 2017-09-25 07:28:24 +00:00
Agetian
4da83ff5f8 - Unbanning Partner commanders in Planar Conquest. 2017-09-25 07:21:39 +00:00
Agetian
d5cf8848fa - Some fixes for AF Explore. 2017-09-25 07:18:37 +00:00
Agetian
4ea6f9dd6a - Scripts update: use DamageDealtOnce for cards that say "whenever X deals damage" 2017-09-25 07:10:21 +00:00
Agetian
d8027b002d - Script update: DealtCombatDamageOnce -> DamageDealtOnce|CombatDamage$True 2017-09-25 06:58:33 +00:00
Agetian
623cda83d5 - Script update: CombatDamageDoneOnce -> DamageDoneOnce|CombatDamage$True 2017-09-25 06:46:19 +00:00
Agetian
99b3e4493b - Documenting changes in CHANGES.txt. 2017-09-25 06:42:12 +00:00
Agetian
025a201a7d - Convert DamageDone|OnlyOnce to the new trigger DamageDoneOnce. Remove code for DamageDone|OnlyOnce (no longer needed). 2017-09-25 06:41:13 +00:00
Agetian
3243555181 - Partner commander UI support for mobile Forge (both Constructed and Planar Conquest). 2017-09-25 06:29:47 +00:00
Agetian
867eae442b - Fix compile. 2017-09-25 05:40:49 +00:00
Hanmac
e8e80a7ac8 replace lifelink with generic damage trigger, and do lifelink there 2017-09-25 05:21:06 +00:00
Sol
480c88a73e Tempest Caller only targets opponents 2017-09-24 22:38:45 +00:00
Agetian
6f34a42034 - A somewhat less confusing Boneyard Parley AI. 2017-09-24 15:01:58 +00:00
Agetian
312421ed28 - ReplaceProduceMana: check the root ability for tap cost (in case the mana ability is a subability), fixes interaction of mana replacement effects (e.g. Mana Reflection) with cards that tap in the root ability (e.g. Nykthos, Shrine to Nyx). 2017-09-24 14:57:28 +00:00
Indigo Dragon
93e81fdc01 Gave a custom Energy icon to Journeyman Skin 2017-09-24 14:27:23 +00:00
Agetian
2d7f0f907b - Added StackDescription to Grim Captain's Call. 2017-09-24 14:13:54 +00:00
Agetian
6aca67efa6 - Made Boneyard Parley AI playable. 2017-09-24 14:07:06 +00:00
Indigo Dragon
bab2b9f528 Changed "CARDNAME can block an additional creature." to CARDNAME can block an additional creature each combat."
Notes: Possible weird interactions when giving a multiblock creature an additional block eg. Equipping a Night Market Guard with echo circlet. Further study required.
2017-09-24 12:33:45 +00:00
Indigo Dragon
07886140fb Several Ixalan changes, as well as adding Scry reminder texts to those that need them.
Notes: Grim Captain's Call is... weird. Needs fixing for the stack panel when cast (Largely because it doesn't target the creatures on cast, only returns them on resolution)

Also: Boneyard Parley works when the Human uses it, but AI absolutely butches the execution I can't explain. Added SVar:RemAIDeck:True just in case. Pls fix.
2017-09-24 11:55:39 +00:00
Agetian
90341ee27a - Some more ID cleanup in Ravnica. 2017-09-24 04:50:54 +00:00
Agetian
f78da4f637 - Some improvements to DoNotDiscardIfAble discard AI for corner cases, to avoid (very rare) situations where the AI would not discard anything or crash. 2017-09-24 04:48:07 +00:00
Agetian
d3b8ffe328 - Fixed Heartless Pillage. 2017-09-24 04:47:11 +00:00
Agetian
e0b25527b3 - Fixed a deck ID in the Ravnica quest world. 2017-09-24 04:32:22 +00:00
Agetian
56798a76c9 - Fixed generated text for Volrath's Shapeshifter. 2017-09-23 16:07:28 +00:00
Agetian
2faab75bd8 - Changed comment type. 2017-09-23 15:44:08 +00:00
Agetian
d4d7c5b35e - Removed the issue note for As Foretold from ISSUES.txt, it's an implementation issue that does not have a functional side effect for the end-user. There's a TODO entry in the relevant part of the code. 2017-09-23 15:43:18 +00:00
Agetian
bf07df8f7b - Fixed Oracle text of Terror of Kruin Pass. 2017-09-23 15:40:00 +00:00
Agetian
3622103a90 - Cavern of Souls AI: do not try to pay with the SA the card for which was already tapped, for this cost or for something else. 2017-09-23 14:12:48 +00:00
Agetian
31680b3849 - Preparing Forge for Android publish 1.6.3.001 [incremental/new release]. 2017-09-23 13:52:31 +00:00
Blacksmith
aa7611fceb Clear out release files in preparation for next release 2017-09-23 13:51:19 +00:00
Blacksmith
9f0a34455b [maven-release-plugin] prepare for next development iteration 2017-09-23 13:45:20 +00:00
Blacksmith
da51f8af37 [maven-release-plugin] prepare release forge-1.6.3 2017-09-23 13:45:13 +00:00
Blacksmith
170853f2cc Update README.txt for release 2017-09-23 13:43:19 +00:00
Agetian
df4b625ac4 - Adapted the DigEffect implementation of Explore until the effect is fully converted and it can be removed. 2017-09-23 11:58:47 +00:00
Hanmac
2d6ff3b74c basic Explore Effect 2017-09-23 11:47:57 +00:00
Agetian
a5b3b61052 - Fixed Scry AI scrying away basic lands thinking that they do not produce mana since they do not use a mana-producing SA as such and rely on the basic land type instead. 2017-09-23 11:16:58 +00:00
Agetian
1b1a56e77c - Improved support for Illusions-Donate, added deck The Great and Powerful Trixie 2, changed the deck The Great and Powerful Trixie 3 to be a more standard Legacy-legal Trix. 2017-09-23 09:04:16 +00:00
Agetian
e18dd07491 - Fixed Axis of Mortality. 2017-09-23 05:21:27 +00:00
Agetian
f94771730e - AI: Improved logic for Capsize. 2017-09-23 05:09:16 +00:00
Sol
cb24df8890 Fix Bloodcrazed text 2017-09-23 03:14:27 +00:00
Sol
499d72d5d6 - Fix SpellDescription for Deadeye Tracker 2017-09-23 03:10:42 +00:00
Sol
1bab2617b7 W16 isn't in Standard 2017-09-22 23:47:57 +00:00
Agetian
b5ed2daa81 - Some changes for the experimental Intuition logic as support for Illusions-Donate. 2017-09-22 19:36:48 +00:00
Agetian
c32cd456b6 - Improved AI logic for Firecannon Blast. 2017-09-22 14:55:17 +00:00
Agetian
0f9a71f07d - Sword of X and Y artifact cycle: added MustBeBlocked:AttackingPlayer since otherwise the AI generally ignores these powerful effects, typically losing to them or to the card advantage that they generate in a matter of few turns. 2017-09-22 09:06:48 +00:00
Agetian
a1711daead - Some improvements in ComputerUtilCombat regarding predicting attack clone effects and MustBeBlocked. 2017-09-22 09:04:47 +00:00
Indigo Dragon
5b6031f41d Policy: If a card was printed without a reminder text in a booster expansion or preconstructed deck (eg Commander), it no longer requires reminder text. IMA removes basic reminder texts for serval cards, like the mythic rares archangel_of_thune.txt and thundermaw_hellkite.txt 2017-09-22 08:29:03 +00:00
Agetian
c3787ab02f - A little improvement to the previous commit. 2017-09-22 05:55:17 +00:00
Agetian
9d1a216a20 - AI: Predict clone on attack effects like Tilonalli's Skinshifter when deciding which creatures to leave for blocking. 2017-09-22 05:53:49 +00:00
Hanmac
2603d08aa4 add more Damage Once Triggers 2017-09-22 05:36:33 +00:00
Agetian
990c0afee2 - Updated the Protection effects to use the timestamp-based changed keywords mechanism, this fixes interactions like Lignify + Reverent Mantra choosing Protection from green. 2017-09-22 05:00:54 +00:00
Sol
1bfd401ed7 Fix description for Verdant Sun's Avatar 2017-09-22 01:11:22 +00:00
Sol
22414bab7c Remove Defender from Lightning Rig Crew 2017-09-22 00:31:39 +00:00
Agetian
08222b0d5c - Moved the code fragment a bit. 2017-09-21 18:17:36 +00:00
Agetian
d599d29514 - ReplaceDamage: according to 119.8, if something would deal 0 damage, it deals no damage at all, and thus there is no event to replace (please double check). 2017-09-21 18:08:33 +00:00
Agetian
e5120c7074 - Updated RankingScraper.py to use the draftsim ranking scraper instead of the defunct bestiaire ranking scraper. 2017-09-21 17:54:51 +00:00
kevlahnota
68f36bf172 Rotate split cards when zoomed-in, text detail for effect and emblem instead of token 2017-09-21 14:29:08 +00:00
Agetian
ee025f9a13 - Asset file size correction. 2017-09-21 11:58:17 +00:00
Agetian
68891d18f4 - Some improvement to the experimental and currently disabled "hold land drops" AI logic. 2017-09-21 04:38:54 +00:00
Agetian
be4b7e7232 - A somewhat better implementation of "show another card in prompt" code that potentially allows extension later on for things that may need it. 2017-09-21 03:50:42 +00:00
Agetian
c3e03c17e8 - Deadeye Tormentor: Raid description format unification (em-dash style). 2017-09-21 03:38:05 +00:00
Sol
1dc63294b4 Missing Raid in description of Deadeye Tormentor 2017-09-21 03:12:50 +00:00
Sol
63191515d3 Catch much wider exceptions in buildCardGenDeck based on key card 2017-09-21 01:28:48 +00:00
Agetian
f8b072f14a - Added a (hacky) way to display the remembered card for a SA in the prompt panel, currently used by the Explore workaround implementation, but may be used for more (in cases where the wrong thing is displayed on mouse-over in complex SAs). 2017-09-20 19:37:59 +00:00
Agetian
d09862522b - Fixed capitalization in Iconic Masters definition file. 2017-09-20 17:54:59 +00:00
Agetian
ce5240223b - Updated quest opponent deck Guybrush Threepwood 2. 2017-09-20 17:46:49 +00:00
Agetian
5b8d2cb36a - AI: For Vraska, the Relic Seeker, play a little more conservatively to avoid losing the planeswalker by activating its -3 ability two times in a row 2017-09-20 17:43:52 +00:00
Agetian
d9b35ca1ee - Added XLN planeswalker decks to quest precons. 2017-09-20 15:24:21 +00:00
Agetian
e8433a1d80 - Fixed AI params for Makeshift Munitions. 2017-09-20 11:52:43 +00:00
Agetian
092aac82ea - SetStateAi: allow AILogic$ Always on SetState transform triggers (set this on Vance's Blasting Cannons). 2017-09-20 11:51:16 +00:00
Agetian
9039178927 - AI: Look in all graveyards for potential targets of Deathgorge Scavenger, starting with the opponents' graveyards and then ending with the AI's own. 2017-09-20 09:51:27 +00:00
Agetian
a8b2a627f8 - Added an AI deck hint to Deadeye Quartermaster. 2017-09-20 09:34:14 +00:00
Agetian
da8b85decd - AI: try to proc Raid in case an attack is likely instead of casting the creature early in main 1 2017-09-20 09:19:04 +00:00
Agetian
73786bfd94 - Do not use the old AE ligature conversion in column names of the item manager. 2017-09-20 05:36:30 +00:00
Agetian
e06ea82f62 - Some improvements to AnimateAi.
- Fixed Myth Realized ability text. Also, it shouldn't be a characteristic-defining ability since it's an ability granted to itself (point 4 in rule 604.3a), otherwise it doesn't interact correctly, for example, with Starfield of Nyx in presence of five enchantments.
2017-09-19 17:37:57 +00:00
Agetian
085732868c - Minor improvement to the experimental "hold land drops" AI option (currently disabled by default). 2017-09-19 16:42:59 +00:00
Agetian
3f7c512c63 - Improved AI cast params for Fleetwheel Cruiser. 2017-09-19 16:42:26 +00:00
Agetian
28d8b49de7 - Minot tweak to previous commit. 2017-09-19 15:16:27 +00:00
Agetian
c79d04f29a - Added an experimental logic for Intuition (currently disabled by default, pending testing and fine-tuning). 2017-09-19 15:15:58 +00:00
Agetian
d1ba022ce9 - Tweaked the deck Morkus Rex 3 a bit. 2017-09-19 12:37:07 +00:00
Agetian
d36fb16b12 - Migrating XLN cards from upcoming to the real folders. 2017-09-19 12:04:06 +00:00
Agetian
26e94d3ba6 - Minor tweak to quest opponent descriptions. 2017-09-19 03:24:45 +00:00
Agetian
d2cd5e7202 - ScryAi: in the general case, avoid activating before the end of opponent's turn before own turn if it requires tapping the source (especially if it's on a creature) and has a mana cost. 2017-09-19 03:22:45 +00:00
Agetian
0bfd5cc292 - Added quest opponents Guybrush Threepwood 1, Guybrush Threepwood 2, Morkus Rex 2, Morkus Rex 3 with Ixalan cards (Pirate and Dino themed, respectively). 2017-09-18 19:05:02 +00:00
Agetian
deb25f8299 - Added a reference to Gishath, Sun's Avatar. 2017-09-18 18:51:22 +00:00
Agetian
aab28fd3fd - [XLN] Fixed ability description for Captain Lannery Storm. 2017-09-18 17:03:41 +00:00
Agetian
d9759305a3 - Do not calculate damage to planeswalkers for the purpose of chump saving in case the AI profile specifies to always consider them threatened if something is attacking them. 2017-09-18 15:49:08 +00:00
Agetian
e015e57dda - Try to detect cases where the actual damage to planeswalkers will be zero after prevention. 2017-09-18 15:29:14 +00:00
Agetian
44a2f1880d - Some improvements to the "protect planeswalkers with weak chumps" logic. Enabling it in a conservative version by default.
- Several additional experimental toggles for ChangeZoneAllAi, will be used to fine-tune "bounce all" type spells in generic cases.
2017-09-18 15:26:15 +00:00
Agetian
2e3617ad1c - Storm Seeker: added a missing SVar reference. 2017-09-18 12:51:51 +00:00
Agetian
ae5e7d1e60 - One more tweak to the previous commit. 2017-09-18 12:47:56 +00:00
Agetian
a31c3dc611 - Readded a part of the reanimator-specific Survival of the Fittest code. 2017-09-18 12:47:17 +00:00
Agetian
2ee3ec303d - Fixed Survival of the Fittest AI, which was accidentally made to grab discard targets from the library, lol 2017-09-18 12:46:11 +00:00
Agetian
3d8a85a145 - A tweak to Skyshroud War Beast NeedsToPlay (may still not work reliably in multiplayer, depending on who the AI chooses; ideally could use its own AI logic or something). 2017-09-18 12:20:54 +00:00
Agetian
fc18153f02 - A tweak to DigUntilAi DontMillSelf. 2017-09-18 12:19:02 +00:00
Agetian
77c1875b6b - [XLN] Fixed River Herald's Boon mana cost. 2017-09-18 09:29:45 +00:00
Agetian
4388a84787 - Gaze of Adamaro: marked as AI-playable. 2017-09-18 06:30:32 +00:00
Agetian
0d3a9c8b16 - Some logic for cards like Sudden Impact that deal damage to the player by the number of cards in his hand. 2017-09-18 06:19:50 +00:00
Hanmac
2ca05b5634 add TriggerDamageDealOnce 2017-09-18 05:28:02 +00:00
Agetian
4569bca39d - ChangeZoneAllAi: for things that return creatures from the battlefield to wherever en masse (e.g. Evacuation), cast is as a last resort if the opponent is about to deal lethal damage and win.
- Skyshroud War Beast: added NoZeroToughnessAI param.
2017-09-18 04:35:14 +00:00
Agetian
c401a609a4 - Survival of the Fittest, Treasure Trove: added an AI cast preference parameter to avoid having a duplicate on the battlefield.
- Expanded AI cast preferences to potentially support cards the duplicate for which can be cast in case the opponent debuffed the original (currently not used by any cards, looking for good candidates).
2017-09-18 04:15:41 +00:00
Agetian
07a71f1059 - Survival of the Fittest AI: avoid mana lock by casting it at the end of opponent's turn. 2017-09-18 03:40:03 +00:00
Agetian
82f6f7d4d5 - [XLN] Assorted fixes. 2017-09-18 03:32:26 +00:00
Agetian
97f68a8247 - Removed a hard limit on playing Survival on the Fittest (needs a better solution, currently too restrictive and the AI will not cast another SotF even if the first one becomes locked in some way on board, e.g. Imprisoned on the Moon). 2017-09-17 20:06:21 +00:00
Agetian
1d9f867c75 - Added an implementation comment. 2017-09-17 19:50:53 +00:00
Agetian
ebd3c33051 - Added some simple SVar-based prediction of Reanimator decks, currently used by the Survival of the Fittest AI code.
- Added a worlds.txt entry for Kamigawa quest world.
2017-09-17 19:50:03 +00:00
Agetian
00cec4faa0 - Documenting changes in CHANGES.txt. 2017-09-17 18:42:25 +00:00
Agetian
690fa7bf78 - Added Kamigawa quest world by daitokujibiko. 2017-09-17 18:40:19 +00:00
Agetian
ace6474157 - Survival of the Fittest: no point in casting the second copy while the first one is active and in play. 2017-09-17 18:33:03 +00:00
Agetian
974a017044 - [Request] Improved AI logic for Spike Weaver. 2017-09-17 18:23:34 +00:00
Indigo Dragon
89917d4f75 Added CostDesc$ {5}{R}{R}{R} to mizzixs_mastery.txt
Removed some double spaces "  " for the rest.
2017-09-17 16:05:18 +00:00
Agetian
f651364e00 - [XLN] A couple fixes. 2017-09-17 12:35:52 +00:00
Agetian
2a8b43b7a3 - Comment fix. 2017-09-17 10:46:01 +00:00
Agetian
aff991690d - Improved DigUntil logic for Hermit Druid. 2017-09-17 10:43:55 +00:00
Agetian
7f095947ab - ShouldPumpCard: when testing for Infect, ensure that the opponent can actually receive poison counters. 2017-09-17 08:29:43 +00:00
Agetian
b18b2cff44 - PumpAi (Aristocrats logic): only check Infect in case the target can receive Poison counters. 2017-09-17 08:23:14 +00:00
Agetian
befd16238b - Arcane Adaptation: should not affect Sideboard. 2017-09-17 08:16:11 +00:00
Agetian
20d3e540b0 - One more tweak to the previous commit. 2017-09-17 08:09:37 +00:00
Agetian
cede2927c5 - Made the new experimental AI option chance-based (currently disabled by default). 2017-09-17 08:07:56 +00:00
Agetian
543aae0893 - Some tweaks and improvements in the experimental AI code. 2017-09-17 08:02:43 +00:00
Agetian
e9c55345b8 - Fixed Heartless Pillage.
- Fixed some non-UTF-8 symbols in scripts.
2017-09-17 07:45:21 +00:00
Agetian
8c7dc08844 - Added an experimental option to hold unused land drops until main 2 (disabled by default). 2017-09-17 06:22:30 +00:00
Agetian
3ae20885c4 - Removed and defaulted the experimental AI option to avoid counting static attack bonus effects in declare blockers twice. 2017-09-17 04:47:27 +00:00
Agetian
09042f1dbf - Minor code reorganization in ComputerUtilCard. 2017-09-17 04:44:07 +00:00
Agetian
6b5c52d2c8 - Fixed the AI ignoring the non-stacking KW list and repeatedly pumping cards with non-stacking keywords without any other benefit when attacking for lethal. 2017-09-17 04:17:28 +00:00
Agetian
e02e2c462b - Added Menace to non-stacking keywords list (fixes multiple AI activations). 2017-09-17 04:07:50 +00:00
Agetian
30a1ea7a4f - [XLN] Some fixes. 2017-09-17 03:56:45 +00:00
austinio7116
51a2383574 Fixed fathom fleet cutthroat typo 2017-09-16 20:39:18 +00:00
Agetian
7fbbb9b030 - A somewhat better variable name. 2017-09-16 16:24:10 +00:00
Agetian
f2d88796ed - A somewhat better timing for Thundering Wurm for the AI. 2017-09-16 15:55:34 +00:00
Agetian
1659ca88ca - Added AI logic for Mox Diamond. 2017-09-16 15:48:16 +00:00
Agetian
fd7ba65203 - Improved the AI detecting whether the attacking creature would be destroyed by blockers when it has first strike or double strike (fixes e.g. a useless power pump vs. a first striker). 2017-09-16 15:29:42 +00:00
Agetian
ae66502fd5 - Fixed the em-dashes in CardFactoryUtil code. 2017-09-16 15:28:55 +00:00
Agetian
cc06fb6b40 - Fixed a failing test. 2017-09-16 14:53:17 +00:00
Agetian
a60c6af30e - [XLN] Fixed Gishath, Sun's Avatar and Ranging Raptors. 2017-09-16 14:32:43 +00:00
Agetian
91e7cd6576 - [XLN] A couple fixes. 2017-09-16 11:46:51 +00:00
Agetian
d717a68445 - Documenting changes in CHANGES.txt. 2017-09-16 11:40:24 +00:00
Agetian
7f4dcf54a7 - AiBlockController: shifted priority for non-lethal gang blocks below necessary chump blockers to avoid cheating or dying accidentally to something. 2017-09-16 11:35:46 +00:00
Agetian
613238e0f9 - Fixed the interaction of Tribute with effects like Solemnity. 2017-09-16 11:04:53 +00:00
Agetian
72f7189aeb - Added a comment. 2017-09-16 10:46:06 +00:00
Agetian
ea2616434c - No need to choose the best possible creature for a non-lethal Menace block. 2017-09-16 10:45:19 +00:00
Agetian
0261f25996 - AiBlockController: added a routine to try to block a Menace creature with two creatures that don't kill it but neither of which get killed as well. 2017-09-16 10:41:37 +00:00
Agetian
f5db79ce69 - Added a patch to the Starter world by Seravy. 2017-09-16 10:25:06 +00:00
Agetian
236a7c91d5 - Comment fix. 2017-09-16 10:18:05 +00:00
Agetian
c6bae2116a - Added an extra mode to DamageDone trigger (OnlyOnce$ True) that tries to count the damage only once. Currently will do it in combat, but will not yet do it for noncombat simultaneous damage like Aura Barbs (feel free to improve).
- Effectively this replaces the double trigger setup for Enrage and other cards that require such a count (e.g. Fungusaur and friends).
2017-09-16 10:17:14 +00:00
Indigo Dragon
05d42d9518 Replace many " - " with "—" 2017-09-16 06:56:28 +00:00
austinio7116
19a6236fa9 Readding card based deck gen .dat files post rotation 2017-09-16 06:21:15 +00:00
austinio7116
115dbc8b12 Trying to update card based deck generation dat files 2017-09-16 06:17:23 +00:00
Agetian
03084c3ce3 - Moving IMA rankings before XLN rankings in order to maintain the historical order of sets that the file is arranged in. 2017-09-16 05:48:52 +00:00
Agetian
0260afa068 - Changed underscores to spaces in Ixalan draft rankings (hopefully it wasn't intentional that way?...) 2017-09-16 05:47:15 +00:00
Agetian
2f9044f53f - Added a missing reference. 2017-09-16 05:18:12 +00:00
Agetian
f195e85fb8 - Improved the cleanup for Siren's Ruse. 2017-09-16 05:17:57 +00:00
Agetian
65065343f9 - [XLN] Added Siren's Ruse. 2017-09-16 05:14:46 +00:00
Agetian
50c8014977 - Hostage Taker: added Artifact.Other to ChangeZone targeting constraints. 2017-09-16 04:21:18 +00:00
Agetian
911d0a4fae - Vance's Blasting Cannons: the trigger is optional 2017-09-16 04:18:40 +00:00
Agetian
d1bb81e404 - Arcane Adaptation: set it to affect Sideboard too for situations with cards you own outside the game (http://magicjudge.tumblr.com/post/165378548299/the-locust-god-official) 2017-09-16 04:17:06 +00:00
Sol
76526a0b2d No need for duplicating creature types 2017-09-16 03:19:33 +00:00
Sol
a9020d92ca Updated XLN for Blocks and Formats 2017-09-16 01:35:14 +00:00
Sol
74be6e6c14 Adding remaining XLN cards from Marek (P-Z) 2017-09-16 01:33:44 +00:00
Sol
f203168542 Adding remaining XLN cards from Marek (A-O) 2017-09-16 01:33:01 +00:00
Sol
3c03f5fd71 Adding Trilobite type 2017-09-16 01:30:17 +00:00
Sol
8cfc9d7793 Fixing Ixalan editions file 2017-09-16 00:17:48 +00:00
kevlahnota
ec5c500b8f Fix lag I introduced. Just learned that we should never create an instance of a new object inside the render call, because it will create a new instance every draw call. 2017-09-15 20:53:46 +00:00
austinio7116
8f7cbeb4ab First draftsim rankings added for XLN 2017-09-15 20:32:46 +00:00
Agetian
fb32273477 - Corrected a token name. 2017-09-15 16:42:45 +00:00
Agetian
1be49e8d8a - ComputerUtilCombat: attempt to prevent the combat AI from counting the Flanking debuff twice when predicting the power bonus of blocker. 2017-09-15 16:39:20 +00:00
Agetian
b7edc01952 - Working out some TODO items in Ixalan card scripts (treasure token images, previously unconfirmed P/T). 2017-09-15 16:12:37 +00:00
Agetian
0470878ddf - KLD+ Fat packs should be named Bundle. 2017-09-15 16:08:21 +00:00
Agetian
d07a65d553 - Bundle and Booster Box info for Ixalan. 2017-09-15 16:00:18 +00:00
Agetian
58218f10da - Added booster generation info to Ixalan.txt. 2017-09-15 15:57:59 +00:00
Agetian
690eebb6a1 - Added Trilobite creature type. 2017-09-15 15:52:58 +00:00
Agetian
9bd9936781 - Fixed capitalization in Ixalan.txt. 2017-09-15 15:40:01 +00:00
Indigo Dragon
bf62325329 Added Ixalan.txt Edition File 2017-09-15 15:38:08 +00:00
Agetian
a3775e0b57 - Fixed Deathgorge Scavenger and improved AI logic for it. 2017-09-15 15:38:02 +00:00
Agetian
03c0794b48 - Some improvements to the Aristocrats and Electrostatic Pummeler AI logic. 2017-09-15 15:08:31 +00:00
Agetian
b5edcaf5ac - Added a missing reference to Liliana, Defiant Necromancer. 2017-09-15 14:54:37 +00:00
kevlahnota
3723a4e656 Update fastReplace (return null if source string is null) 2017-09-15 14:46:29 +00:00
Agetian
b332038722 - Fixed the AI trying to flip Sasaya, Orochi Ascendant endlessly without satisfying its condition.
- Several script fixes from Marek.
2017-09-15 14:08:27 +00:00
Agetian
33df8dd5eb - PermanentAi: commented out the check of Planeswalker uniqueness by subtype (no longer relevant past Ixalan). 2017-09-15 09:48:29 +00:00
kevlahnota
7e982b327b Refactor some String.replace to use TextUtil.fastReplace 2017-09-15 09:01:58 +00:00
Agetian
91585464bd - [XLN] Added 4 cards by Marek. 2017-09-15 06:35:52 +00:00
Agetian
3c3f494034 - Simultaneous combat damage pass 2: "X is dealt combat damage" card update 2017-09-15 06:34:44 +00:00
Agetian
87cbc6229a - Fixed Soul Link. 2017-09-15 05:59:45 +00:00
Agetian
bd10364d27 - Simultaneous combat damage pass 1: "deals combat damage to X" card update 2017-09-15 05:52:08 +00:00
Agetian
36721dde56 - A tweak to the previous commit. 2017-09-15 05:14:05 +00:00
Agetian
6a74bd841a - Improved TriggerCombatDamageDoneOnce to propagate the amount of damage dealt to targets.
- Corrected Armadillo Cloak and Fungusaur as implementation examples for simultaneous combat damage (simultaneous noncombat damage like Aura Barbs is still impossible as of yet, feel free to improve if you know how).
2017-09-15 05:12:55 +00:00
Agetian
093b5451c3 - Some XLN fixes. 2017-09-15 04:12:13 +00:00
Agetian
896dff3fdc - [XLN] Added 15/09 cards by Marek. 2017-09-15 04:09:12 +00:00
kevlahnota
e8e23603c3 Rotate Plane/Phenomenon on FOptionPane 2017-09-14 22:47:56 +00:00
Agetian
426fe0fe40 - Scavenging Ooze doesn't need remember/cleanup to operate.
- Animation Module: set AILogic to AlwaysAtOppEOT to ensure the AI doesn't manalock itself by spamming it.
2017-09-14 19:12:02 +00:00
Agetian
3e2ef43a0a - [XLN] Added 7 cards by Marek.
- Some card script corrections by Marek.
2017-09-14 18:59:51 +00:00
Agetian
a73b480b1f - [XLN] Updated Enrage cards such that the trigger fires only once in case of simultaneous combat damage. 2017-09-14 13:43:21 +00:00
Agetian
4d374dd587 - Fixed a miswrite in laneswalker achievements, 2017-09-14 13:26:00 +00:00
Agetian
3b97f8c396 - Mobile Forge: fixed a long-standing bug which caused a match (e.g. Planar Conquest) to restart even after a victory when using Space or Enter shortcut keys (when running on PC or when using buttons on a mobile device that are bound to Space and/or Enter). 2017-09-14 09:42:17 +00:00
Agetian
e7a559327c - A tweak in CHANGES.txt. 2017-09-14 08:28:32 +00:00
Agetian
ede35abe1a - A tweak in CHANGES.txt. 2017-09-14 06:25:05 +00:00
Agetian
edc36f915d - Documenting changes in CHANGES.txt. 2017-09-14 06:24:42 +00:00
Agetian
05a9d457aa - Improvements to Counter AI. 2017-09-14 06:18:05 +00:00
Agetian
cc7ce6bae9 - Removed a debug print line in Scry AI. 2017-09-14 05:51:15 +00:00
Agetian
57e5135346 - Improvements to Scry AI. Also, made Scry AI configurable using several AI profile preferences. 2017-09-14 05:35:35 +00:00
Agetian
74980f84d5 - A more generic implementation of AF MustAttack. 2017-09-14 04:19:03 +00:00
Agetian
fa7f1cc21a - [XLN] Added Axis of Mortality, Brazen Buccaneers, Desperate Castaways, Dire Fleet Hoarder, Imperial Lancer, Skulduggery. Added some deck hints and technical info to some XLN scripts. 2017-09-14 03:56:05 +00:00
kevlahnota
8c90fb3c15 Planchase Mod for mobile Forge. Uses BG art of current planes when a player planeswalk. 2017-09-14 00:45:52 +00:00
kevlahnota
021a48c070 The string replacement for achievement collection is not needed. 2017-09-14 00:42:39 +00:00
Agetian
8be583083e - Added XLN planeswalker achievements. 2017-09-13 18:45:28 +00:00
Agetian
8d15a7249a - A fix in planeswalker achievement file. 2017-09-13 18:41:59 +00:00
Agetian
6259793f39 - Fixing a mistype in achievement file names. 2017-09-13 18:41:07 +00:00
Agetian
772faf8cd2 - Updated rankings for AKH and HOU. 2017-09-13 18:25:52 +00:00
Agetian
dd7141aeaa - Adding initial rankings for Iconic Masters. 2017-09-13 18:21:59 +00:00
Agetian
521a3f9341 - Adding Iconic Masters to blocks.txt.
- Adding Ixalan to blocks.txt (currently disabled until all the relevant information about the set is revealed).
2017-09-13 18:15:44 +00:00
Agetian
452bdd7b4f - NPE prevention in ControlGainAi. 2017-09-13 16:41:10 +00:00
Agetian
37ebb5731d - Added Ydwen Efreet.
- Forge is now at 100 unsupported cards total.
2017-09-13 16:35:11 +00:00
Agetian
933ce64cce - Fixed a NPE when trying to repeat last add card before anything has been actually added at least once. 2017-09-13 15:41:12 +00:00
Agetian
b8d0019ece - Added Dulcet Sirens. 2017-09-13 15:37:33 +00:00
Agetian
4df3da0856 - [XLN] Added Charging Monstrosaur, Kinjalli's Caller, Tilonalli's Knight. 2017-09-13 15:22:44 +00:00
Agetian
59f482e52e - Fixed one more logical error resulting in suboptimal/aggressive random trades. 2017-09-13 15:13:03 +00:00
Agetian
538ea82899 - AiBlockController: fixed a logic error in random trade code that led to overly aggressive trades 2017-09-13 15:09:12 +00:00
Agetian
0b7fc67c3f - Experimental: for Dingus Egg, do not check the time stamp of the card that changed zone (fixes interaction with Sacred Ground). Should this be the default behavior for ChangesZone triggers that do not seem to care for how many times the card had changed zones before the trigger resolves? 2017-09-13 14:52:09 +00:00
Agetian
7a92712f0b - As Foretold: allow to cast spells from command zone via its ability. 2017-09-13 14:48:50 +00:00
Agetian
6ed9a8b336 - A little tweak to the previous commit. 2017-09-13 14:25:44 +00:00
Agetian
24df4e78c7 - Random favorable trades on block: when evaluating face-down Morph of Manifested creature, evaluate it based on its original face, not the face down 2/2 one. 2017-09-13 14:12:32 +00:00
Agetian
a0abaf62b4 - [XLN] Added Trove of Temptation. This card is ugly, so certain side effects in corner cases are probably still possible. Currently implemented as a keyword-like ability similar to "no more than X creatures can attack each turn" etc., but maybe is better as a global rule, I'm not sure (and not sure how to properly convert it to a global rule either...). Assistance and improvements are welcome. 2017-09-13 08:59:35 +00:00
Agetian
85ece1ec63 - [XLN] Added Ruthless Knave. 2017-09-13 08:42:10 +00:00
Agetian
ba284aafe3 - [XLN] Added Snapping Sailback by azcotic. 2017-09-13 08:35:25 +00:00
Agetian
f25ab2f892 - [XLN] Added Lightning-Rig Crew, Raiders' Wake, Sword-Point Diplomacy 2017-09-13 08:32:30 +00:00
Agetian
b026111ef8 - Attempting to fix generation of keyword text with long descriptions. 2017-09-13 06:46:18 +00:00
Agetian
9850f2e242 - [XLN] Added Sky Terror. 2017-09-12 17:02:59 +00:00
Agetian
9313fa9193 - [XLN] Added Adanto Vanguard, Captivating Crew, Dire Fleet Ravager, River Sneak, Spell Swindle 2017-09-12 16:58:50 +00:00
Agetian
f19c9183f0 - Decouple MustAttackEntity from MustAttackEntityThisTurn for Alluring Siren. 2017-09-12 16:34:37 +00:00
Agetian
5af3384b02 - Improved implementation for Alluring Siren. 2017-09-12 11:07:46 +00:00
Agetian
46a5512209 - Fixed mana cost for Conqueror's Galleon. 2017-09-12 10:51:41 +00:00
Agetian
63c83e97c4 - [XLN] Added Makeshift Munitions and Vicious Conquistador. 2017-09-12 09:00:24 +00:00
Agetian
cf311fdeb1 - Fixed Alluring Siren implementation. 2017-09-12 08:16:54 +00:00
Agetian
04ef82bc70 - [XLN] Added Legion Conquistador. 2017-09-12 03:26:04 +00:00
Agetian
7182ad2e9c - Some more NPE prevention in getSpellAbilityPriority. 2017-09-12 02:56:34 +00:00
Agetian
072508e1c1 - Added a TODO comment. 2017-09-11 19:12:47 +00:00
Agetian
91101a7c40 - Added a TODO comment 2017-09-11 19:12:33 +00:00
Agetian
d34fa71797 - [XLN] Added Dowsing Dagger / Lost Vale, Legion's Landing / Adanto, the First Fort. 2017-09-11 18:04:42 +00:00
Agetian
bee7bccc12 - A couple XLN card fixes / updates. 2017-09-11 17:48:44 +00:00
Agetian
74eea096b1 - Added some more experimental combat AI options, currently disabled by default.
- Enabling some of the previously experimental, now tested combat AI options (random favorable trades, holding combat tricks until block, trading to save a planeswalker, attempting to avoid attacking into certain blocks).
2017-09-11 17:37:43 +00:00
Indigo Dragon
02c028584b Added Iconic Masters.txt 2017-09-11 15:17:04 +00:00
Agetian
2a87d338b4 - Removed a superfluous comment. 2017-09-11 14:17:19 +00:00
Agetian
8759532964 - Experimental: in combat AI, try to avoid counting static abilities that grant power bonuses twice in case the creature has already attacked and the relevant bonus is already accounted for in getNetPower/getNetToughness (aims to fix AI prediction of creature power in presence of cards such as War Horn). 2017-09-11 14:16:02 +00:00
Agetian
f332db93ad - Added a couple final declarations in PumpAi. 2017-09-11 11:08:21 +00:00
Agetian
6ffc687174 - Some improvements to Aristocrats PumpAi and Electrostatic Pummeler AI.
- Added some final declarations in SpecialCardAi.
2017-09-11 11:06:39 +00:00
Agetian
ba6079164c - Attempting to fix a concurrent exception on an accidental quick reentry of addAttackingBand. 2017-09-11 09:55:26 +00:00
Agetian
372ee945de - Added puzzle PC_122215 coded by Xitax. 2017-09-11 04:32:41 +00:00
Agetian
0f0eed3de6 - A minor tweak/fix in As Foretold script code. 2017-09-10 16:54:26 +00:00
Agetian
9eddb37c3e - As Foretold: a simpler check for split half CMCs. 2017-09-10 16:50:19 +00:00
Agetian
6a262ea604 - A hacky workaround for the interaction between As Foretold and split cards. Seems to work in general cases, for both non-Aftermath and Aftermath splits, but a better solution is most certainly needed and welcome. 2017-09-10 16:25:32 +00:00
Agetian
94693f530e - Documenting changes in CHANGES.txt. 2017-09-10 15:11:33 +00:00
Agetian
ee9eced602 - Integrating PTK Quest world by Xyx. 2017-09-10 15:09:05 +00:00
Agetian
58fa9b8182 - Added a known issue note to As Foretold card text. 2017-09-10 14:40:05 +00:00
Agetian
89b377ab8f - Added a difficult to resolve known issue about As Foretold to ISSUES.txt. 2017-09-10 14:36:50 +00:00
Agetian
1d93fccbb3 - As Foretold: simplified implementation of copyWithDefinedMana 2017-09-10 13:42:16 +00:00
Agetian
f8786bcd47 - Ancestral Vision and similar cards should also be castable without paying their mana cost (117.6a). 2017-09-10 11:46:34 +00:00
Agetian
8adf2043f7 - Added puzzle PS_HOU8 coded by Nigol. 2017-09-10 11:35:46 +00:00
Agetian
8af3fe3db8 - Attempting to fix an issue with fastlands and slowlands ETBing in the wrong state when entering the battlefield together with other lands at the same time.
- Fixed an issue with LastStateBattlefield/LastStateGraveyard not returning anything unless at least some ability has been played.
2017-09-10 11:32:42 +00:00
Agetian
09c1db9afe - As Foretold: allow interaction with spells that initially have no mana cost (e.g. Ancestral Vision). 2017-09-10 11:28:07 +00:00
Indigo Dragon
2f738ff2b7 Updated Rarity corrections 2017-09-10 08:07:16 +00:00
Agetian
4429e36c3f - Improved copyWithDefinedMana such that the defined mana cost is added to spells that initially have no mana cost.
- TODO: it's still impossible to cast e.g. Ancestral Vision via As Foretold, even though it should be possible according to a ruling. Not sure how to fix, help is welcome.
2017-09-10 07:53:27 +00:00
Agetian
0b8a3cfcea - Emblems should be colorless. 2017-09-10 06:19:07 +00:00
Agetian
c3d43f6021 - Use CARDNAME in the desc of As Foretold. 2017-09-10 06:02:28 +00:00
Agetian
2a5badbc7c - Added As Foretold. 2017-09-10 05:54:43 +00:00
Agetian
3c5f24f99d - Fixed Shefet Monitor text. 2017-09-10 03:23:18 +00:00
Agetian
105212a667 - Fixed interaction between Animate Dead and Worldgorger Dragon. 2017-09-10 03:21:49 +00:00
Agetian
f5decd2221 - Improvement to the PumpAI Aristocrat logic. 2017-09-09 17:15:41 +00:00
Agetian
48bd65cd3b - Do not double the Infect damage when predicting it (already accounted for later) 2017-09-09 16:07:29 +00:00
Agetian
90046b11af - Card name fix. 2017-09-09 15:51:23 +00:00
Agetian
6eede614f8 - [XLN] Added Lurking Chupacabra, Wildgrowth Walker. 2017-09-09 15:51:07 +00:00
Agetian
8cd9c5e8a3 - Minor formatting fix. 2017-09-09 15:35:46 +00:00
Agetian
0ef1019d1c - Implemented a simple logic for Aristocrats (Vampire Aristocrat, Bloodthrone Vampire, Nantuko Husk and friends).
- Improved prediction of Infect damage for the AI in shouldPumpCard.
2017-09-09 15:34:35 +00:00
kevlahnota
f2a55e525b Enhanced rotated display of Planes/Phenomenon 2017-09-09 13:35:37 +00:00
kevlahnota
0f9eecd821 Added UI Setting (Mobile Forge) for rotated zoomed image display for Planes and Phenomenon cards. 2017-09-09 10:20:06 +00:00
Indigo Dragon
a45848cbe3 Added reminder text to chief_engineer.txt and maelstrom_nexus.txt.
Also changed modular so that it now has the correct "dies" wording on trigger.
2017-09-09 10:05:35 +00:00
Agetian
07af7d7f97 - [XLN] Renamed Shapers of the Nature -> Shapers of Nature 2017-09-09 04:53:56 +00:00
Agetian
87186ca940 - Territorial Hellkite: fixed Oracle text. 2017-09-09 03:35:29 +00:00
Agetian
990bd7e291 - AiController: attempting to fix a "Comparison.sort violates general contract" error which is likely caused by an internal NPE inside the sort method (difficult to prove if this really fixes the issue since I can't reproduce the problem yet, though it had been reported twice already). 2017-09-09 03:14:34 +00:00
Agetian
c39aa35d36 - Territorial Hellkite: further tweaks to the implementation for the corner cases where it can't attack or attacks someone else. 2017-09-09 03:00:15 +00:00
Agetian
3b187d75bb - Territorial Hellkite: improve interaction when it can't legally attack. 2017-09-08 20:06:30 +00:00
Agetian
84694773a2 - Added Territorial Hellkite. 2017-09-08 19:55:51 +00:00
Agetian
e8d9083f05 - [XLN] Added Bishop of the Bloodstained, Merfolk Branchwalker.
- Added some deck hints to XLN cards.
- Minor corrections in a couple XLN cards.
2017-09-08 18:49:56 +00:00
Agetian
25ccebe617 - [XLN] Added Rile. 2017-09-08 16:55:45 +00:00
Agetian
c6d3d07b3c - [XLN] Added Vona, Butcher of Magan. 2017-09-08 16:34:51 +00:00
Agetian
c6078f9314 - [XLN] Fixed the -3 ability for Vraska, Relic Seeker. 2017-09-08 16:26:34 +00:00
Agetian
db5462b79f - [XLN] Added Vraska, Relic Seeker. 2017-09-08 16:26:08 +00:00
Agetian
3b8e6a2c3a - [XLN] Added Dinosaur Stampede, Rampaging Ferocidon, Raptor Hatchling. 2017-09-08 16:18:07 +00:00
Agetian
eae7e79ecf - Added SpellDescription to an ability in Search for Azcanta / Azcanta, the Sunken Ruin. 2017-09-08 16:04:51 +00:00
Agetian
5264873645 - [XLN] Added Search for Azcanta / Azcanta, the Sunken Ruin.
- Minor fix in Welcome to the Fold.
2017-09-08 16:03:21 +00:00
Indigo Dragon
6009f097cd Fixed some Preconstructed Intro decks 2017-09-08 15:49:01 +00:00
Agetian
96265e5ac1 - Added a couple cards to the AI's library in Pauper Puzzle #03 to avoid a possibility of decking the opponent. 2017-09-08 12:41:01 +00:00
Agetian
5ee33a35fc - Lazav, Dimir Mastermind: copy the non-LKI card (fixes interaction with cards dying to something like Nameless Inversion; however, I'm not sure if LKI was intentional here or not) 2017-09-08 12:40:11 +00:00
Agetian
55f3c484f2 - [XLN] Added Lookout's Dispersal, Ranging Raptors, Ravenous Daggertooth, Storm Fleet Spy 2017-09-08 12:37:33 +00:00
Agetian
0bd678318c - Fixed Otepec Huntmaster. 2017-09-08 11:58:42 +00:00
Indigo Dragon
89ab049391 Changed some keyword reminder texts so that they are more dynamic with {%d:numbers).
Also updated suspend reminder text in CardFactoryUtil. It used to have this weird stock "Three Time Counters". Now it's fixed. Except it has Suspend counters in "1 counters" instead of "a counter"/"3 counters" instead of "three counters". Any improvements are welcome.
2017-09-08 11:07:58 +00:00
Indigo Dragon
aa6e4c5b53 Added reminder texts for Delve, Dredge, Split second, Devoid, Totem armor, Afflict, Bushido, Poisonous, Rampage, Cascade, Conspire, Dredge (again), Flashback*.
*Flashback required changes to the actual Flashback code. Now it works with an em-Dash. I'll possibly apply similar changes to other keywords with em-Dash cast mode.
2017-09-08 08:24:46 +00:00
Agetian
ec8a75be52 - Fixed Burning Sun's Avatar Oracle text. 2017-09-08 04:42:05 +00:00
Agetian
3ae7fe7f04 - Added Arguel's Blood Fast / Temple of Aclazotz, Chart a Course, Commune with Dinosaurs, Fell Flagship, Grazing Whiptail, Growing Rites of Itlimoc / Itlimoc, Cradle of the Sun, Otepec Huntmaster, Skittering Heartstopper, Thundering Spineback.
- Temporary implementation of an Explores trigger linked to AB Dig (until AB Explore is implemented).
2017-09-08 04:41:35 +00:00
kevlahnota
3e4b5830be Fixes Warning in Log - unmappable character for encoding UTF-8 2017-09-07 22:43:28 +00:00
Agetian
8ea28dad68 - [XLN] Added Duskborne Skymarcher. 2017-09-07 18:20:56 +00:00
Agetian
17c1fc79e1 - Fixed spells with Bestow not getting "unbestowed" when they are countered and go to graveyard. 2017-09-07 18:08:51 +00:00
Agetian
801eaaaf37 - A couple minor fixes. 2017-09-07 13:10:56 +00:00
Indigo Dragon
bfd72d7540 Added capital letters to the Protection cards with unique protections 2017-09-07 11:04:59 +00:00
Agetian
1341ef0c3e - Updated token-images.txt 2017-09-07 05:41:21 +00:00
Agetian
95bda0d4bb - Preparing Forge for Android publish 1.6.2.007 [hotfix]. 2017-09-07 05:34:09 +00:00
Agetian
adf412ea74 - Removed SacMe from Puzzleknot cards, doesn't work exactly as was expected. 2017-09-07 05:32:08 +00:00
Agetian
a00a6734b3 - Further tweaks to surprise pump experimental feature. 2017-09-07 05:27:33 +00:00
Agetian
e410eab336 - Fixed Priest of the Wakening Sun, improved default prompt for UnlessCost that is switched. 2017-09-07 03:50:28 +00:00
Agetian
1367357e68 - Updated puzzle PC_120815. 2017-09-06 19:10:03 +00:00
Agetian
4cb4b3bb19 - Preparing Forge for Android publish 1.6.2.006 [incremental]. 2017-09-06 18:04:00 +00:00
Agetian
ce92ab77ec - Enable a simple AI discard cost preference for Heir of Falkenrath. 2017-09-06 18:02:34 +00:00
Agetian
e365fc60bc - Removed Favorable Winds from upcoming (reprinted card). 2017-09-06 17:43:56 +00:00
Agetian
edb1343676 - For the time being, hiding the latest change to attack controller behind an experimental option, enabled in the experimental profile (may be defaulted and removed as a profile option later after extensive testing). 2017-09-06 16:51:32 +00:00
Agetian
f1426103ff - An important tweak to the previous commit. 2017-09-06 16:43:50 +00:00
Agetian
7a767327c0 - AiAttackController: don't attack into a guaranteed block unless the attacker has some kind of an attack/combat effect. 2017-09-06 16:21:49 +00:00
Agetian
e2f9139aa9 - One more tweak to the dev menu (mobile Forge). 2017-09-06 15:40:46 +00:00
Agetian
a80b504379 - A couple tweaks to dev panel. 2017-09-06 15:40:07 +00:00
Agetian
d73b3c44e3 - More work and parameter tweaking for attack/block trade experimental AI. 2017-09-06 15:32:24 +00:00
Agetian
c05f54c105 - Fixed non-UTF-8 encoding in CardFactoryUtil. 2017-09-06 15:25:20 +00:00
Agetian
c917bafde5 - [XLN] Added Regisaur Alpha. 2017-09-06 15:18:53 +00:00
Agetian
b349106f08 - Ixalan creature type update (Dinosaur subtype according to Sep 6 notes) 2017-09-06 15:16:14 +00:00
Indigo Dragon
1c91f345fc Added em-Dash for Suspend 2017-09-06 15:03:49 +00:00
Agetian
2b11afbb2d - More tweaks to the attack/block trading experimental options. 2017-09-06 14:30:24 +00:00
Agetian
e9479dd24f - Using em-dash for Awaken generated description. 2017-09-06 14:24:29 +00:00
Agetian
d676412291 - Fixed description generation for KW Devour. 2017-09-06 14:19:49 +00:00
Indigo Dragon
5fd5d032ef Updates to Keywords again 2017-09-06 13:56:27 +00:00
Agetian
66ee09f656 - Added NeedsToPlayVar to Explore. 2017-09-06 13:50:29 +00:00
Indigo Dragon
8f57a55286 Updates to keywords
So it's not the End of the World
2017-09-06 12:47:46 +00:00
Agetian
6498250114 - NPE prevention in CardZoom (still need to figure out why sometimes [seemingly randomly] the card on stack will become "castable" in mobile Forge in the first place). 2017-09-06 12:06:12 +00:00
Indigo Dragon
bf7b9a7057 CHANGES TO KEYWORDS !!!DANGER!!!
I'm messing with powers I don't understand. Here I go.

Following changes to menace that removed reminder text by removing the keyword.equals("Menace"), I conclude that you can add reminder text by inserting the keyword.equals("Keyword"). This is part one if it works, as it is for all the simple keywords that don't rely on numbers or costs (Compare Devoid with Awaken)

If this doesn't work, and everything breaks, burn it.
2017-09-06 12:06:01 +00:00
Agetian
8de7099c0f - A little tweak to the "attack to trade" experimental code. 2017-09-06 11:36:02 +00:00
Agetian
c2b3c4919d - [XLN] Added Favorable Winds, Storm Fleet Aerialist, Storm Fleet Arsonist. 2017-09-06 11:26:55 +00:00
Indigo Dragon
90b5531041 Updated Keyword Reminder Texts
Notes: Technically, all the reminder texts here were the texts as described by Magic Comprehensive Rules 702. Keyword Abilities. However nobody actually follows the rules when making simple reminder text for actual cards. If people are confused over advanced situations where creatures become noncreature permanents but the reminder text specifies "creature", they should consult the overriding Comprehensive rules. Therefore; I feel justified in simplifying these 'reminder' texts.
2017-09-06 10:47:33 +00:00
Indigo Dragon
e57de3268f illusionists_stratagem.txt Removed double "Draw a Card."
street_spasm.txt Added a full stop.
2017-09-06 09:46:35 +00:00
Agetian
b9e68834de - A little improvement in Suggest basic land count feature. 2017-09-06 08:50:46 +00:00
Agetian
5352514c2b - KeepTapped Attach AI logic: try not to activate on things that can't tap. 2017-09-06 05:07:49 +00:00
Agetian
aa58b3df35 - [XLN] Added a couple deck hints. 2017-09-06 04:37:42 +00:00
Agetian
dfe5a90b06 - [XLN] Added Savage Stomp and Wily Goblin. 2017-09-06 04:36:06 +00:00
Agetian
893486eb93 - Reorganized addCardToZone a little bit. 2017-09-06 04:35:38 +00:00
Agetian
bdc684d2d1 - Fixed Dev mode Add Card to X functions. 2017-09-06 04:14:24 +00:00
Agetian
80bc435df6 - Added Dinosaur creature subtype to lists/TypeLists.txt 2017-09-06 03:34:44 +00:00
kevlahnota
00cbfa45d7 Minor tweak in InputSelectTargets and some string refactoring 2017-09-05 20:53:54 +00:00
Agetian
5c18270826 - ComputerUtilCost: sac costs (Sac<...>) use ";" as a delimiter and not "," (fixes e.g. the AI never sacrificing anything to Defiant Salvager).
- Added SacMe to Puzzleknot cards.
2017-09-05 19:35:35 +00:00
Agetian
aa86ef631b - DeckGenUtil: some improvements to the suggest basic lands feature. 2017-09-05 19:09:06 +00:00
Agetian
c67a2bd458 - Added an Explores trigger (not used anywhere yet, can be used in the upcoming Explore effect later). 2017-09-05 18:05:33 +00:00
Agetian
f796ec32a7 - [XLN] Added Deadeye Quartermaster, Kitesail Freebooter, Overflowing Insight, Pirate's Cutlass. 2017-09-05 17:55:13 +00:00
Agetian
9ce43ea7a6 - [XLN] Added 5 cards. 2017-09-05 16:19:42 +00:00
Agetian
19702794e4 - Volcano Hellion: tweak the script such that it allows to choose any number. 2017-09-05 15:25:30 +00:00
Agetian
9015bfcac3 - Minor dev panel button movement. 2017-09-05 12:12:18 +00:00
Agetian
ad13ef9187 - Dev Mode: split the "Add Card to Play" functionality into two buttons: "Add Card to Battlefield", which acts like other Add Card to X buttons and adds the card directly to the battlefield, without using the stack and without firing ETB triggers; and "Cast Spell/Play Land", which acts like the old "Add Card to Play" button and uses the stack when necessary and fires all triggers. 2017-09-05 12:06:37 +00:00
Agetian
4a1b147a25 - A little improvement in script execution code in GameState. 2017-09-05 03:37:33 +00:00
Agetian
5970908a3f - GameState: added a way to precast from a custom script line for the needs of Puzzle Mode. 2017-09-05 03:35:52 +00:00
Agetian
e3d37ca831 - Reverted an inadvertent experimental commit. 2017-09-05 03:27:29 +00:00
Agetian
4c9afb8c80 - Fixed puzzle PP18. 2017-09-05 03:27:08 +00:00
Agetian
253049ec5a - [XLN] Added Shapers of the Nature. 2017-09-05 03:11:45 +00:00
Agetian
c1d7f86542 - [XLN] Added Drover of the Mighty. 2017-09-05 03:04:56 +00:00
Agetian
aa44fba1e5 - Added 2 puzzles coded by Xitax, 9 puzzles coded by Nigol. 2017-09-05 02:53:28 +00:00
kevlahnota
5dbf2b0ff5 Fix font display in landscape mode (instead of vertical names, it will display names horizontall) Edited 2017-09-05 02:47:55 +00:00
kevlahnota
8fcbaef064 Refactor some strings 2017-09-05 02:44:36 +00:00
Agetian
08c3509823 - [XLN] Added some more cards. 2017-09-04 18:55:56 +00:00
Agetian
d9961bc1a0 - [XLN] Added some more cards + patched up a couple cards a bit. 2017-09-04 18:55:14 +00:00
Agetian
9fd8c1f546 - Basic Survival of the Fittest logic (in addition to it already accounting for DiscardMe cards), most likely needs tweaking. Promoted Survival of the Fittest to RemRandomDeck from RemAIDeck. 2017-09-04 17:32:46 +00:00
Agetian
d265102f31 - [XLN] Added Wakening Sun's Avatar. 2017-09-04 16:20:34 +00:00
Agetian
063a50b14a - Some modification of chance variables in the AI profiles related to the experimental attack/block trade options. 2017-09-04 13:24:56 +00:00
Agetian
ebb06b34b0 - Some more work on attack/block trades [experimental]. 2017-09-04 12:18:06 +00:00
Agetian
bbc3a75e4a - A NPE guard in the experimental pump code. 2017-09-04 09:59:07 +00:00
Agetian
86c5ef363e - A tweak in the lure pump experimental code. 2017-09-04 09:00:23 +00:00
Agetian
daefcab8f5 - A little modification for the previous commit. 2017-09-04 05:42:41 +00:00
Agetian
3d5f9beac5 - ChangeZoneAi: implemented AILogic SacAndRetFromGrave, added it to Recurring Nightmare. 2017-09-04 05:40:27 +00:00
Agetian
0b01855bf4 - Maniacal Rage: promoted to RemRandomDeck. 2017-09-04 04:58:59 +00:00
Agetian
cc78904582 - GameState: support for targeting scripted abilities that use a Defined$ parameter originally (for Puzzle Mode needs). 2017-09-04 04:32:25 +00:00
Agetian
306b4652eb - Fixing a potential crash in the experimental holdCombatTricks code. 2017-09-04 04:10:41 +00:00
Agetian
721a629ba8 - LifeInDanger AI: account for special cases with Worship and Elderscale Wurm 2017-09-03 18:58:51 +00:00
Agetian
4f00aeeed7 - Fixed a quest opponent deck file. 2017-09-03 15:57:32 +00:00
Agetian
88ca671965 - Some more work on surprise attacks/trades [experimental, disabled by default]. 2017-09-03 15:26:28 +00:00
Indigo Dragon
7c3cfe1326 Updated all the Overload Reminder Texts
Some now use ValidDescription$
2017-09-03 15:05:02 +00:00
Agetian
735f1007cf - AB$ Cost 0 -> DB$ in Festering Mummy. 2017-09-03 14:44:57 +00:00
Agetian
66df063e2a - Added ValidDescription to Street Spasm. 2017-09-03 14:44:35 +00:00
Indigo Dragon
e7547905a3 Magus of the Wheel had a weird Name:Meteor Blast in its oracle text 2017-09-03 13:59:08 +00:00
Agetian
206b01376d - Added unfinished and inoperable Online Play mode to ISSUES.txt (known issues). 2017-09-03 13:04:03 +00:00
Agetian
e50ef893e8 - [XLN] Added Huatli, Warrior Poet. 2017-09-03 12:55:47 +00:00
Indigo Dragon
496cc470e2 Update all the Prowl and Reinforce cards from MOR.
They now pretend to be keywords better.
2017-09-03 11:54:55 +00:00
Agetian
b82516ba4d - Minor improvement related to my previous commit. 2017-09-03 11:19:23 +00:00
Agetian
bbe7ff3c9b - Further work on surprise pump spells on attack [experimental, disabled by default, may still cause issues when enabled]. 2017-09-03 11:12:48 +00:00
Indigo Dragon
d33770f107 Update Convoke Reminder text to include the "(Your creatures can help cast this spell. ", similar to Improvise.
Also updated Frenzy Sliver and Virulent Sliver. Frenzy Sliver is done, while Virulent sliver needs a few more tweaks in an upcoming Keyword update.
2017-09-03 09:50:04 +00:00
Indigo Dragon
6523500d09 Update arcanum_wings.txt so that it now acts more like a keyword.
PrecostDesc$ -> Costdesc$, Added forward brackets and costs to Spelldescription$
2017-09-03 09:29:13 +00:00
Agetian
1a7a5fe6a8 - Fixed some AB$ Cost 0 -> DB$ declarations in card scripts.
- Fixed Nova Pentacle (the card needs some additional combat AI support though, or the AI will suicide its own creatures if the opponent has nothing on the battlefield).
2017-09-03 08:55:26 +00:00
kevlahnota
9426c531e3 More Refactoring of String.format 2017-09-03 08:11:28 +00:00
Indigo Dragon
35a6977df8 Gave seeds_of_renewal.txt the Undaunted Keyword in the Oracle text 2017-09-03 08:05:10 +00:00
Agetian
c06280a1ea - [XLN] Added 3 cards. 2017-09-03 04:59:10 +00:00
kevlahnota
8b3ff137d1 Refactor strings enclosed in parentheses, transform P/T to strings 2017-09-03 02:01:34 +00:00
Agetian
0fa826f926 - Minor formatting tweak. 2017-09-02 18:26:10 +00:00
Agetian
5c7b40248c - A more generic solution for concatWithSpace / concatNoSpace. 2017-09-02 18:24:30 +00:00
kevlahnota
7a3a8b0490 Refactor some "String.format" to use StringBuilder 2017-09-02 17:35:13 +00:00
Agetian
78d13749d5 - AbilityUtils: fixed compilation (please check, replaced CardUtil.getPluralType with CardType.getPluralType) 2017-09-02 17:06:12 +00:00
Agetian
1143a43d30 - Attempting to improve LifeGain AI for creatures with negative SA activation costs like Spike Feeder. 2017-09-02 17:00:10 +00:00
Hanmac
3cdf553142 moved hardcoded type plural into text file and moved the getPlural and getSingular functions into CardType class 2017-09-02 16:55:11 +00:00
Agetian
f9569895b4 - Hermit Druid: added AILogic$ DontMillSelf 2017-09-02 16:31:07 +00:00
Indigo Dragon
905b469515 Updated Eternalize keyword so now it's similar to Embalm, as well as specifying that it gains the Zombie type, not replaces 2017-09-02 14:53:15 +00:00
Indigo Dragon
aac3443b78 Updated Madness Reminder Text 2017-09-02 14:46:44 +00:00
Indigo Dragon
bd9b05463e Changing the mana cost 2 to {2} for the [[Rout]] ability
K:You may cast CARDNAME as though it had flash if you pay 2 more to cast it. -> K:You may cast CARDNAME as though it had flash if you pay {2} more to cast it.

This may break something. If so, whoops
2017-09-02 14:27:29 +00:00
Agetian
bb82c6fa02 - Further work on trick/lure attacks with held pump spell. 2017-09-02 14:19:34 +00:00
Indigo Dragon
7232f9446a Replaced all double spaces __ with single spaces _ 2017-09-02 14:17:43 +00:00
Agetian
639f2b8e02 - [XLN] Fixed Fathom Fleet Captain 2017-09-02 13:50:21 +00:00
Agetian
7d860542b1 - [XLN] Added Bellowing Aegisaur. 2017-09-02 12:55:17 +00:00
Agetian
7593dad6c8 - Some more improvements for block-baiting into a pump. 2017-09-02 11:32:52 +00:00
Agetian
ecc4834110 - Adding hyphenated emblem names to the rest of the planeswalker files. 2017-09-02 11:11:57 +00:00
Agetian
2e1adc6f9b - Documenting changes in CHANGES.txt. 2017-09-02 11:08:12 +00:00
Agetian
18a0866735 - Code support for hyphens in Planeswalker emblem names. 2017-09-02 11:05:51 +00:00
Indigo Dragon
4036fc44e3 More brackets [] and colons : for planeswalkers
That should be it for now.
2017-09-02 11:05:25 +00:00
Indigo Dragon
9f7bbd6efc Upgrade Tamiyo, the Moon Sage ai emblem name 2017-09-02 10:41:45 +00:00
Indigo Dragon
1ff11a09ed Added Colons to Planeswalkers
Added dashes to some Emblem Names
Added Brackets for some select walkers
2017-09-02 10:39:51 +00:00
Hanmac
be3d4723ef Emblem fixed startwith 2017-09-02 10:28:07 +00:00
Agetian
190703d74b - Some changes in PumpAi related to the in-dev experimental features. 2017-09-02 10:05:17 +00:00
Indigo Dragon
eac361d892 Gave Colons to kytheon_hero_of_akros.txt 2017-09-02 06:32:10 +00:00
Indigo Dragon
a772cf9910 Changes to Emblems
Emblems now read "Emblem Planeswalker" instead of "Planeswalker emblem"
Image name now is "emblem_planeswalker"

If this breaks anything please revert.

Also added some colons to oracle texts for some planeswalkers.
2017-09-02 05:52:02 +00:00
Indigo Dragon
77eab9e6f8 Changed vessel_of_malignity.txt 2017-09-02 04:44:30 +00:00
Agetian
c4fc168369 - Minor comment fix. 2017-09-02 04:00:29 +00:00
Agetian
41409fb5c1 - An AI option to hold combat tricks until block, experimental and disabled by default (more work to follow soon). 2017-09-02 03:57:06 +00:00
Agetian
71eb88eb7b - Fixed Claim // Fame. 2017-09-02 03:03:27 +00:00
Agetian
dcbde94d78 - [XLN] Added 8 cards from Planeswalker decks. 2017-09-01 17:09:38 +00:00
Agetian
11bbcf5b42 - More work on "surprise trade on block" project (experimental, disabled by default). 2017-09-01 15:45:17 +00:00
Agetian
badf68b80a - Further work on random trades (experimental, disabled by default.) 2017-09-01 14:53:44 +00:00
Agetian
01e1c2ab0a - Added Prowess, Outlast and Afflict to CreatureEvaluator. 2017-09-01 14:02:55 +00:00
Agetian
bef51732e0 - Fixed an inverted clause in the experimental "random combat trades" code. 2017-09-01 13:42:12 +00:00
Agetian
77046cf38a - Fogwalker: removed empty lines, AB with cost 0 -> DB 2017-09-01 13:35:24 +00:00
Agetian
2a623957c3 - A little tweak to the previous commit. 2017-09-01 08:49:30 +00:00
Agetian
65322925a4 - Pump effects: consider beneficial combat pump effects when checking if the AI should pump the card. Also, use "gains flying until EOT" defensively if there's a chance to kill the opponent's creature or block the opposing flyer with an indestructible flying creature. 2017-09-01 08:31:50 +00:00
Agetian
346c4db3d9 - Whispers of the Muse, Treasure Trove: added AILogic$ AlwaysAtOppEOT as it's a more optimal default timing for this effect. 2017-09-01 07:56:42 +00:00
Agetian
7212698863 - Fabrication Module: added AILogic. 2017-09-01 07:29:06 +00:00
Agetian
1779544d70 - Further work on experimental attack/block trade feature (disabled by default). 2017-09-01 05:54:01 +00:00
Agetian
0270420da8 - Some minor refactoring in AiBlockController related to the previous commit. 2017-09-01 03:22:11 +00:00
Agetian
a1461851ee - Some further work on the experimental attack/block trading options (disabled by default in all profiles except Experimental). 2017-09-01 03:19:24 +00:00
Agetian
eaabad923e - Reverted a change to Edgar Markov that breaks it for tribal Vampire spells. 2017-09-01 03:10:17 +00:00
Agetian
a9e12f5aca - Added final to several vars in PlayerControllerHuman. 2017-08-31 19:14:55 +00:00
Agetian
0637d5511d - Some experimental attacking and blocking changes related to surprise trade attacks and blocks, currently disabled by default in all profiles except Experimental. 2017-08-31 19:11:04 +00:00
Agetian
27682b2d07 - Electrostatic Pummeler AI: predict static damage prevention 2017-08-31 18:05:30 +00:00
Agetian
0982852563 - Fixed Nazahn, Revered Bladesmith. 2017-08-31 12:51:08 +00:00
Agetian
8147a315c2 - Added rudimentary AI to requested cards Erratic Portal and Null Brooch. Helm of Possession appears to be AI playable already after testing, so marking it as such. 2017-08-31 12:41:54 +00:00
Agetian
fdf787e9b4 - Integrating latest Oracle updates by Indigo Dragon. 2017-08-31 09:52:40 +00:00
Agetian
01d2338388 - [XLN] Added Queen's Bay Soldier. 2017-08-31 08:46:45 +00:00
Agetian
1dfec40f49 - [XLN] Added Hostage Taker, Sorcerous Spyglass and Tishana, Voice of Thunder. 2017-08-31 05:08:08 +00:00
Agetian
a82602faae - [XLN] Added Star of Extinction and Vanquisher's Banner. Fixed the card name in Shapers' Sanctuary. 2017-08-31 04:22:03 +00:00
Agetian
c2aac371ae - Cleaned up imports. 2017-08-30 19:28:08 +00:00
Agetian
9aa6e0d39a - CloneAi: improved a bit for Tilonalli's Skinshifter. 2017-08-30 19:27:12 +00:00
Agetian
3c177c4ebf - [XLN] Added Angrath's Marauders, Captain Lannery Storm, Sunbird's Invocation, Tilonalli's Skinshifter. 2017-08-30 19:13:46 +00:00
Agetian
d8b090bae2 - Added quest opponents Jafar 2 (mono B zombies with some Afflict) and Heinz Doofenshmirtz 2 (URG Electrostatic Pummeler). 2017-08-30 18:29:20 +00:00
Agetian
928792bea7 - Some method renaming. 2017-08-30 17:17:10 +00:00
Agetian
99775194fd - Electrostatic Pummeler AI: teach the AI to pump the Pummeler to save it from dying to direct damage spells. 2017-08-30 17:16:00 +00:00
Agetian
beb73828e0 - LifeSetAi: Do not use this ability in case the amount the life is set to is the same as the AI's current life (e.g. Oketra's Last Mercy while the AI is already at the starting 20 life). 2017-08-30 16:52:20 +00:00
Agetian
87dc72b874 - [XLN] Added Bloodcrazed Paladin, Boneyard Parley, Fathom Fleet Captain, Ruin Raider, Vraska's Contempt. 2017-08-30 16:12:27 +00:00
Agetian
528ae68282 - Preparing Forge for Android publish 1.6.2.005 [incremental]. 2017-08-30 15:18:49 +00:00
Agetian
a44031cd39 - [XLN] Added Old-Growth Dryads, Shaper's Sanctuary coded by azcotic 2017-08-30 15:14:55 +00:00
Agetian
af922d5171 - Added Deeproot Champion and Waker of the Wilds coded by azcotic [fixed versions]. 2017-08-30 15:06:43 +00:00
Agetian
bd6599a309 - Better Electrostatic Pummeler AI. 2017-08-30 14:55:01 +00:00
Agetian
2e062b1ad6 - [XLN] Added Kopala, Warden of Waves and River's Rebuke. 2017-08-30 05:45:48 +00:00
Agetian
5fc6654045 - [XLN] Added Arcane Adaptation, Daring Saboteur, Dreamcaller Siren, Entrancing Melody, Herald of Secret Streams. 2017-08-30 05:30:59 +00:00
Agetian
32c3494098 - Added Know Evil. 2017-08-30 04:49:13 +00:00
Agetian
379461820b - Adhering to Java 7 feature set for the purpose of Android compatibility. 2017-08-30 03:19:23 +00:00
Agetian
424cb7a9f2 - [XLN] Fixed Admiral Beckett Brass card name. 2017-08-30 03:17:19 +00:00
Agetian
b41e2b6f51 - Electrostatic Pummeler AI: don't overpump when using it defensively. 2017-08-29 16:56:42 +00:00
Agetian
84e73f6f52 - Fixed the AI cheating with Plague Belcher by ignoring its triggered ETB ability. 2017-08-29 16:18:09 +00:00
Agetian
39fb42db10 - Enabling evasion prediction for assault and attrition attack declarations in default AI profiles after testing. 2017-08-29 16:05:20 +00:00
Agetian
85c18ce07b - Somewhat better RememberedWithSharedCardTypes, hopefully would work more reliably in case of 3+ cards tested at the same time for shared card type [not currently seen on any card]. 2017-08-29 15:57:30 +00:00
Agetian
3fe548d8b7 - [XLN] Added Rowdy Crew. 2017-08-29 15:49:30 +00:00
Agetian
eb32222d39 - [XLN] Added Ashes of the Abhorrent, Mavren Fein Dusk Apostle, Sanguine Sacrament, Settle the Wreckage, Tocatli Honor Guard.
- [XLN] Added some AI flags to cards.
- Fixed Hymn of the Wilds description.
2017-08-29 14:32:22 +00:00
Agetian
e70d7ab5c1 - [XLN] Added Deadeye Tormentor, Prosperous Pirates, Sun-Crowned Hunters. 2017-08-29 09:30:05 +00:00
Agetian
a3da735087 - [XLN] Revel in Riches: the effect is not optional. 2017-08-29 09:18:04 +00:00
Agetian
8c12b5a47e - Removed a debug print line. 2017-08-29 09:10:30 +00:00
Agetian
65c27c6c93 - Fixed an issue with non-creature tokens being added twice to different rows, resulting in odd Z order when mousing over them. 2017-08-29 09:03:36 +00:00
Agetian
4c31e1a4b5 - Minor counter abbreviation tweak. 2017-08-29 08:33:25 +00:00
Agetian
7be776d417 - Added spell description to Treasure Map. 2017-08-29 05:56:29 +00:00
Agetian
1a93e8f331 - [XLN] Added Revel in Riches, Sleek Schooner, Treasure Map // Treasure Cove.
- Added Picture SVars to card in Upcoming.
- Fixed Emperor's Vanguard (should work well enough until Hanmac finishes the code for Explore as a separate effect).
2017-08-29 05:46:08 +00:00
Sol
b6107f79dd - Fix Xenagos not having it's Legendary PW applied properly 2017-08-29 02:19:25 +00:00
Agetian
fb0acb6a8b - [XLN] Jace, Cunning Castaway: the clones should be immediately activatable as soon as they hit the battlefield. 2017-08-28 19:26:54 +00:00
Agetian
240a898fe5 - Added a TODO entry for Emperor's Vanguard (needs Explore to be updated for the double clause). 2017-08-28 18:49:59 +00:00
Agetian
3aad5916f5 - [XLN] Added Bishop of Rebirth and Walk the Plank. 2017-08-28 18:44:50 +00:00
Agetian
c2a7f19d38 - [XLN] Added Jace, Cunning Castaway. 2017-08-28 18:35:31 +00:00
Agetian
0a5fe2e2af - Some implementation clarifications. 2017-08-28 18:32:05 +00:00
Agetian
f6e5256ae4 - Planeswalker rule: restore the "go to graveyard on 0 loyalty" functionality. 2017-08-28 18:30:25 +00:00
Agetian
46aefe6b8a - [XLN] For now, commenting out the Planeswalker Rule code since it won't apply post-Ixalan. 2017-08-28 17:52:17 +00:00
Agetian
af7ef111b4 - [XLN] Planeswalker uniqueness no longer matters ( http://magic.wizards.com/en/articles/archive/feature/ixalan-mechanics ), planeswalkers are Legendary instead. 2017-08-28 17:39:45 +00:00
Agetian
c918743187 - [XLN] Fixed Admiral Becket Brass. 2017-08-28 17:37:58 +00:00
Agetian
817a691248 - [XLN] Integrating Oracle updates by Indigo Dragon: Planeswalkers now have the Legendary supertype. 2017-08-28 17:35:53 +00:00
Agetian
04e33448e0 - [XLN] Added Admiral Becket Brass. 2017-08-28 17:30:17 +00:00
Agetian
aa4624e344 - [XLN] fixed a card file name. 2017-08-28 15:52:52 +00:00
Agetian
1b7a819a2b - [XLN] fixed a card file name. 2017-08-28 15:52:15 +00:00
Agetian
096036f2a4 - [XLN] Added 10 cards. 2017-08-28 15:50:49 +00:00
Agetian
b488ff053a - Fixed RevealEffect crashing for cards like Vizkopa Confessor. 2017-08-28 15:21:45 +00:00
Agetian
04f24faf32 - Experimental: attempting to fix card flickering (power/toughness, jumping between rows) by selectively updating the view for P/T and types after all static effects for all cards have been processed instead of aggressively updating the entire state view during each static effect operation (might also make the game run a bit faster on mobile).
- Volrath's Shapeshifter: do not copy the target's set code and rarity.
2017-08-28 14:06:28 +00:00
kevlahnota
c180343853 Remove redundant import statement, also refactor addMissingItems method. -kev 2017-08-28 09:44:18 +00:00
kevlahnota
da0739ab21 Refactored decoding URL. This is much better than using regex/replace method. -kev 2017-08-28 09:23:36 +00:00
kevlahnota
aa1d2a1879 Fixed filenames that contains comma and apostrophe when parsed (as seen on achievement resource when downloaded, "%27s" will be replaced by "'s" and %2C" will be replaced by ","), so they can be viewable on achievement page. 2017-08-28 07:54:15 +00:00
Agetian
9d9ef0069a - Adding an empty edition file for the upcoming set Ixalan. 2017-08-28 06:22:57 +00:00
Agetian
50790b94a3 - Fixed Herald Horn. 2017-08-28 05:50:35 +00:00
Agetian
59b7f9c775 - Somewhat better AI for Exhaustion. 2017-08-28 04:25:19 +00:00
Agetian
0f83f23052 - Preparing Forge for Android v1.6.2.004 [hotfix/incremental]. 2017-08-27 19:03:15 +00:00
Agetian
1da95433ae - Fixed the AI sacrificing everything to Westvale Abbey without honoring AIPreference:SacCost (should also fix other similar cards with sac cost that requires the AI to sacrifice several cards). 2017-08-27 19:00:23 +00:00
Agetian
d0579b5f75 - ChangeCombatantsEffect: attempt to account for the new rule for Ulamog, the Ceaseless Hunger + Portal Mage interaction in multiplayer games. Currently achieved by rigging the triggering info on the stack instance, which may not be optimal. Feel free to propose a better solution. 2017-08-27 18:17:21 +00:00
Agetian
1d19f56de8 - Experimental: do not reset the paid hash in resetOnceResolved since it appears to be cleared in other SA operations when needed, and aggressively clearing it breaks transient payment info (e.g. Orator of Ojutai + revealed Dragon card). 2017-08-27 18:15:19 +00:00
Agetian
3688e13137 - Add Basic Lands dialog: make it show only the sets with actual basic lands in them.
- Minor cleanup in addCardToZone in PlayerControllerHuman.
2017-08-27 18:12:32 +00:00
Agetian
45d71d0c5f - AI improvements: restored playability of Exhaustion, made Cursed Scroll AI-playable, added some additional code support for programmable Exert logic (Ahn-Crop Champion is an example), added a bit of threshold to Living Death AI, moved Momir Vig Avatar logic to SpecialCardAi. 2017-08-27 18:11:06 +00:00
Agetian
d8be84b617 - Added quest world "The Gates of Magic" by Seravy. 2017-08-27 18:07:15 +00:00
Agetian
f7c4fe8e91 - Fixed an issue with launching Forge on mobile for some users. 2017-08-27 17:59:15 +00:00
Goblin Hero
9005598f90 Revert previous commit 2017-08-27 17:53:47 +00:00
Goblin Hero
5e508770ef Readme added for server testing purposes 2017-08-27 17:32:46 +00:00
Agetian
8331f92775 - GameState: added support for counters in exile zone (e.g. via Mairsil) and for face-down cards in exile (e.g. via Bomat Courier). 2017-08-27 04:12:19 +00:00
Agetian
4c3c7e3807 - GameState: use setCounters to clear the player/planeswalker counter count 2017-08-27 03:57:40 +00:00
Agetian
1e39e3e815 - Preparing Forge for Android publish 1.6.2.003 [hotfix]. 2017-08-27 03:15:58 +00:00
Agetian
5f6519d4c6 - Fixed a crash in mobile Forge when pausing application. 2017-08-27 03:15:15 +00:00
Agetian
e93c35bcae - Added 3 puzzles coded by Xitax. 2017-08-27 03:14:51 +00:00
Agetian
a9d522d1d9 - Added 2 puzzles coded by Xitax. 2017-08-26 19:34:03 +00:00
Agetian
44ac42a4b5 - Puzzle Mode: A better solution for precasting Awaken in GameState (allows both Awaken and AwakenOnly). 2017-08-26 19:08:35 +00:00
Agetian
78a60c750a - GameState: when processing a precast effect solely for KW Awaken, only precast the Awaken part, not the parent ability. 2017-08-26 18:54:39 +00:00
Agetian
6012b06a38 - Added 3 puzzles coded by Xitax. 2017-08-26 18:39:50 +00:00
Agetian
3ff370a903 - Fixed a missing reference in Magus of the Mind (matters for Mairsil, the Pretender - fixes a crash when activating a Mairsil'ed copy of the Magus's ability). 2017-08-26 18:30:06 +00:00
Agetian
6147d64a74 - Preparing Forge for Android publish 1.6.2.002. 2017-08-26 18:19:14 +00:00
Agetian
91b839dd26 - Fixed a NPE in ComputerUtil. 2017-08-26 18:17:31 +00:00
Agetian
cdb6d10519 - Genju of the X cycle of cards AI: attempt to avoid tapping the targeted card for mana if there's another source like that available (might be further improved later by detecting the actual mana type and the sources of that type that are open, after which the special AI parameter SVar might no longer be necessary). 2017-08-26 18:01:03 +00:00
Agetian
586abb5466 - Use updateAbilityTextForView as a limited form of updating the text part of the view in order to update changed card text (e.g. lands affected by Blood Moon, creatures affected by Humility) while avoiding flickering side effects.
- Volrath's Shapeshifter: fixed a concurrent modification when removing temporary triggers.
2017-08-26 17:12:08 +00:00
Agetian
710ac14202 - Fixed Neko-Te. 2017-08-26 17:01:46 +00:00
Agetian
a8735d1b4c - Restored a separator space in SettingsPage. 2017-08-26 15:15:07 +00:00
Agetian
f9ede752f2 - Added a new option to mobile Forge which might help deal with the texture issues after locking/unlocking the screen (suggested by kevlahnota). 2017-08-26 15:13:46 +00:00
Agetian
5bdb205168 - Mobile Forge: new game installations enable battlefield texture filtering by default. 2017-08-26 14:58:01 +00:00
Agetian
b606b939a6 - Fixed AI logic name in Oracle's Vault. 2017-08-26 14:53:59 +00:00
Agetian
0e0b12dcc0 - Fixed keyword name capitalization in AiAttackController. 2017-08-26 14:49:03 +00:00
Agetian
b9e7b1f29b - Attack AI: Afflict should be considered as a combat effect for the purpose of attacking into non-lethal blockers. 2017-08-26 14:46:50 +00:00
Agetian
f930f2aa80 - Minor comment clarification. 2017-08-26 06:22:15 +00:00
Agetian
8698f7e4ab - Bomat Courier AI improvement: teach the AI to sac Courier when threatened in case sacrificing it would provide hand card advantage 2017-08-26 06:17:56 +00:00
Agetian
40b62c09f7 - Fixed references in Soul Burn and Drain Life.
- Promoted Wildfire and Burning of Xinye from RemAIDeck to RemRandomDeck.
2017-08-26 04:14:27 +00:00
Agetian
8ddc3ae172 - Fixed Jeskai Charm. 2017-08-25 20:27:26 +00:00
Agetian
b3e2fb4046 - Documenting changes in CHANGES.txt. 2017-08-25 19:28:43 +00:00
Agetian
42558f2bd4 - A better, theme-oriented planeswalker attacker targeting arrows with the default orange-ish color.
- Removed the unused "darker PW arrows" option.
2017-08-25 19:26:13 +00:00
Agetian
1e23b0a17e - Documenting changes in CHANGES.txt. 2017-08-25 17:12:35 +00:00
Agetian
5b6e0d5c10 - CounterAi: a more generic solution for Mental Misstep exclusion, part 2 2017-08-25 15:55:28 +00:00
Agetian
a53de75873 - CounterAi: a more generic solution for counterspells only targeting CMC X vs. CMC X spells. 2017-08-25 15:52:52 +00:00
Agetian
b3dc4671d0 - Fixed the attack AI overevaluating board position because unblocked attackers with evasion were not removed from remainingAttackers before further evaluation. 2017-08-25 15:35:29 +00:00
Agetian
ef4dd57032 - NPE prevention in ComputerUtil. 2017-08-25 14:59:22 +00:00
Agetian
ad1e17f329 - Fixed a NPE in ComputerUtilCost, part 2. 2017-08-25 11:01:48 +00:00
Agetian
2ba53e2dab - Fixed a NPE in ComputerUtilCost. 2017-08-25 10:59:29 +00:00
Agetian
909b8e7127 - Bristling Hydra AI: teach the AI to use a ping activation to save the hydra from death. 2017-08-25 10:57:12 +00:00
Agetian
8280401e10 - A better algorithm for detecting available mana for the purpose of CounterAi. Should be more precise and less cheat-y. 2017-08-25 09:21:02 +00:00
Agetian
6096fc1c92 - Documenting changes in CHANGES.txt. 2017-08-25 06:10:14 +00:00
Agetian
0378d54acc - Added implementation comment. 2017-08-25 05:51:54 +00:00
Agetian
d33b642b8e - A more universal CounterAi fix, accounting for sources producing multiple mana. 2017-08-25 05:49:00 +00:00
Agetian
27d68bcf45 - Fixed CounterAi in the trigger portion as well. 2017-08-25 05:28:06 +00:00
Agetian
b0122a8e38 - Renamed a method to be more self-explanatory. 2017-08-25 05:26:50 +00:00
Agetian
96cbb63c84 - CounterAi: fixed the AI not accounting for the mana in the mana pool of the SA activator; fixed the AI looking at the mana of the wrong opponent in multiplayer matches. 2017-08-25 05:23:27 +00:00
Agetian
bd16899c56 - Reorganized the dev mode panel to make a bit more sense (grouped the buttons by function, more or less).
- Added "Repeat Last Add Card" functionality to dev mode panel.
2017-08-25 04:53:31 +00:00
Agetian
b874a2b76d - Added a fix by Seravy to prevent the large quest shop pools from hanging the game because statistics were updated for them during selling. 2017-08-25 04:11:14 +00:00
Agetian
aaf16273a9 - Minor code style unification. 2017-08-24 17:46:01 +00:00
Agetian
f83e03e775 - Fixed a comment. 2017-08-24 17:45:01 +00:00
Agetian
5328e443db - Fixed the AI tapping a creature for mana to cast a pump instant/sorcery spell on that creature. 2017-08-24 17:44:03 +00:00
Agetian
ed49943039 - Further improvements to AI handling Electrostatic Pummeler. 2017-08-24 17:27:14 +00:00
Agetian
220fdcdc75 - Added an overriding implementation of doTriggerAINoCost to BondAi, fixes the AI randomly ignoring a chance to soulbond creatures. 2017-08-24 16:21:23 +00:00
Agetian
a1b2a5149d - Volrath's Shapeshifter: run a limited update for ability and keyword text such that the changed text is always visualized (e.g. things like Rally triggers). 2017-08-24 15:09:18 +00:00
Agetian
3090a991a6 - Fixed Scalelord Reckoner triggering for cards not on the battlefield. 2017-08-24 13:31:41 +00:00
Agetian
e0cae27f2f - Reverting an inadvertent change. 2017-08-24 13:30:58 +00:00
Agetian
21c7c74b31 - Attempting to fix Dragon Presence cards, part 1: "dragon presence" apparently cares about the current battlefield state, not the LKI (from Gatherer: "you must control a Dragon as you are finished casting the spell to get the bonus. For example, if you lose control of your only Dragon while casting the spell (because, for example, you sacrificed it to activate a mana ability), you won’t get the bonus"). Also fixes Orator Ojutai not drawing a card at all when a Dragon is present on the battlefield. 2017-08-24 13:29:46 +00:00
Agetian
441693ac36 - Added a couple break statements. 2017-08-24 11:49:08 +00:00
Agetian
77910d82fc - Improved AI spending counters for Skullmane Baku. 2017-08-24 11:47:59 +00:00
Agetian
05258a5b48 - Some improvement to the AI for "remove X counters": made the Baku cards that are marked as RemRandomDeck (but not RemAIDeck) playable by the AI again, albeit some of them rather suboptimally. Quillmane Baku is not supported (but was already marked RemAIDeck). 2017-08-24 10:28:43 +00:00
Agetian
387abd1606 - Clarification for the new setting. 2017-08-24 05:51:16 +00:00
Agetian
3e4efc22a6 - Added an option to make planeswalker attacker targeting arrows somewhat darker to make them easier to see on the battlefield.
- Added code support to differentiate stack targeting arrows in color as well (currently not used).
2017-08-24 05:48:17 +00:00
Agetian
2851e51af8 - Fixed Izzet Chemister not having Haste. 2017-08-23 19:38:40 +00:00
Agetian
ad8ad35c03 - One more fix. 2017-08-23 19:37:21 +00:00
Agetian
20d27bb9b3 - Removed an unused parameter. 2017-08-23 19:37:00 +00:00
Agetian
30e878cbf9 - Fixed the previous commit. 2017-08-23 19:36:29 +00:00
Agetian
374f6fcc1b - Improvements to the Electrostatic Pummeler AI logic. 2017-08-23 19:35:45 +00:00
Agetian
4ea7d68bff - Oviya Pashiri event in Kaladesh: added missing lands. 2017-08-23 17:32:41 +00:00
Agetian
88491f61e2 - Some more improvements for Electrostatic Pummeler AI 2017-08-23 17:20:33 +00:00
Agetian
abe710f3ab - Some more fine-grained control over AILogic PayEnergyConservatively. 2017-08-23 15:16:47 +00:00
Agetian
3e4e87db23 - GameState: clear counters on planeswalkers and players before applying new ones from the game state. 2017-08-23 15:01:04 +00:00
Agetian
e7ca1a13a6 - Renamed a method. 2017-08-23 14:47:54 +00:00
Agetian
aca78410f6 - Moved a comment. 2017-08-23 14:47:24 +00:00
Agetian
989331cbb4 - Some improvements for Pump AI related to pump spells with pure energy cost, makes Electrostatic Pummeler a little better in AI's hands. 2017-08-23 14:45:01 +00:00
Agetian
7a8267a772 - A less reckless default AILogic PayEnergyConservatively for Longtusk Cub and Bristling Hydra (gives the AI a chance to use the energy for something else when possible). 2017-08-23 12:53:44 +00:00
Agetian
37ca383360 - Formatting code in DamageAllAi. 2017-08-23 08:52:51 +00:00
Agetian
90eb55fbf8 - Made DamageAllAi more multiplayer friendly + improved the logic for seeing if both a player can be killed and some more creatures can be dealt with in multiplayer environment.
- Marking ComputerUtil.getOpponentFor as deprecated and adding a comment about what it should be replaced with over time. Also, simplified its implementation since at the moment it's a functional synonym of getWeakestOpponent.
2017-08-23 08:51:28 +00:00
Agetian
e96c503b11 - DamageAllAi: prioritize killing the player off, if possible (e.g. Earthquake for X=1 to kill creatures or X=3 to kill a player). 2017-08-23 04:58:29 +00:00
Agetian
99ce6cf29d - isValid: A player who lost the game leaves it and can't be a valid target of spells or abilities. 2017-08-22 06:59:59 +00:00
Agetian
e14952607d - Fixed Kilnspire District. 2017-08-22 06:55:17 +00:00
Agetian
de3efa0fe7 - Fixed a typo. 2017-08-22 06:09:50 +00:00
Agetian
9af123523a - Further improvement to Wand of Ith prompt. 2017-08-22 06:09:31 +00:00
Agetian
7bc5adbde2 - Fixed an extra character at the EOL in Wand of Ith. 2017-08-22 06:08:32 +00:00
Agetian
5287978e2f - Fixed Wand of Ith triggering the opponent's Sangromancer when the Wand's controller forces that opponent to discard a card. 2017-08-22 06:04:46 +00:00
Agetian
d364cc0f51 - Improved the experimental attack evasion prediction by considering creatures with evasion first (further tweaking and testing in progress). 2017-08-22 05:10:11 +00:00
Agetian
e7c30da23c - Improved prompt for Wand of Ith. 2017-08-22 04:33:56 +00:00
Agetian
1e6467cf80 - Experimental: attempting to improve the AI choice for attrition attack when predicting possible opponent's forces with evasion (e.g. Flying). Currently enabled only for the Experimental AI profile for the testing period. 2017-08-21 17:29:57 +00:00
Agetian
12452c9b2a - Fixed the AI profile files. 2017-08-21 17:23:10 +00:00
Agetian
92b985d24d - Experimental: attempting to improve the AI choice for all-in assault for battlefield situations where the defender will have several, but not enough, defenders with evasion (e.g. Flying).
- Currently only enabled for the Experimental AI profile for the testing period.
2017-08-21 17:07:46 +00:00
Agetian
a0f640c739 - Some code maintenance. 2017-08-21 16:10:48 +00:00
Agetian
46ecbc9c42 - Dev Mode: added a new function "Remove Card from Game", which allows to completely remove a card from the game in case it was added previously by mistake. 2017-08-21 16:09:20 +00:00
Agetian
96f97e41e1 - Create a Puzzle: allow to choose to start the game either with the Human or with the AI player taking the first turn.
- Improved the opening warning message for this mode.
2017-08-21 15:52:57 +00:00
Agetian
5726f05531 - Comment fix. 2017-08-21 15:19:00 +00:00
Agetian
b389209c05 - Added a simple Create Puzzle mode to desktop Forge (presents you with a clean battlefield, allows to dump the game state with a template for puzzle metadata).
- Enhanced the Dev Mode feature set with the "Add Card To Library/Graveyard/Exile" commands.
- Ported this Dev Mode functionality, as well as "Exile Card From Hand/Play", to mobile Forge.
2017-08-21 15:17:41 +00:00
Agetian
a82b3fd88a - Removed debug print lines. 2017-08-21 11:25:30 +00:00
Agetian
04a125d20f - A somewhat more conservative and fine-grained approach at improving the token-generation AI logic. 2017-08-21 11:24:47 +00:00
Agetian
fc2e93a57f - Converted token generation ability activation chance for the AI into a profile variable (currently defaults to 100% for all profiles, pending testing). 2017-08-21 10:29:30 +00:00
Agetian
cfe9c17b58 - Experimental: TokenAi: do not use a "80% chance to generate a token" chance when the token-generating ability is otherwise relevant. Prevents the AI from missing activations of planeswalker token generation abilities on casting a planeswalker, as well as indecisively creating tokens via The Hive and other similar cards before Declare Blockers when on defense. Will run some tests with this later, or may convert to an AI profile variable. 2017-08-21 10:19:48 +00:00
Agetian
b24f31f98c - Fixed an issue with the new quest card price format and cards with multiple art index (e.g. Arcane Denial from ALL or basic lands). 2017-08-21 10:04:36 +00:00
Agetian
5263edc3e2 - Minor restructuring/update in Puzzle code. 2017-08-21 09:44:45 +00:00
Agetian
99b80cccad - Unified puzzle description for INQ01. 2017-08-21 06:50:18 +00:00
Agetian
9794c5a188 - Added puzzle INQ03 (Dead Man's Hand #03). 2017-08-21 06:46:36 +00:00
Agetian
2bd19736f4 - Added puzzle INQ02 (Dead Man's Hand #02). 2017-08-21 06:06:55 +00:00
Agetian
13376d0ced - Improved handling of script execution in GameState to support subabilities and KW Awaken (other keywords with mana cost might need similar treatment later). 2017-08-21 04:50:49 +00:00
Agetian
1aa8475295 - Added a special turn ID correction for puzzles that begin right before the beginning of the human's turn (e.g. INQ01).
- Added support for "Gain Control of Specified Permanents" goal type to Puzzle Mode.
2017-08-20 18:55:12 +00:00
Agetian
4bffe22f68 - Added puzzle INQ01 (Inquest Gamer - Dead Man's Hand #01). 2017-08-20 18:36:58 +00:00
Agetian
094941ab8c - Added support for Imprinted cards to GameState. 2017-08-20 17:51:29 +00:00
Agetian
1a1bcc8d5c - Added support for "Play the Specified Permanent" type objective to Puzzle Mode (e.g. Inquest puzzles). 2017-08-20 17:37:54 +00:00
Agetian
e77eb8a563 - Added a new C17 card to Kaladesh plane in Planar Conquest. 2017-08-20 16:17:17 +00:00
swordshine
c063ccfa02 - C17: Added Portal Mage 2017-08-20 15:31:20 +00:00
Agetian
b1ccb5c3de - Fixed a couple weirdly auto-formulated stack/prompt descriptions. 2017-08-20 15:07:11 +00:00
Agetian
29b4704c4b - Reverted adding Shining Shoal (misread how the card works, need prevention from source, will see if I can update the script later). 2017-08-20 11:59:26 +00:00
Agetian
931d7d55dd - Fixed description for Shining Shoal. 2017-08-20 11:45:52 +00:00
Agetian
2df0403c94 - Added Shining Shoal (uses the same scripting strategy as Captain's Maneuver). 2017-08-20 11:45:35 +00:00
Agetian
9cd878bad1 - More Volrath's Shapeshifter QoL. 2017-08-20 05:12:17 +00:00
Agetian
4c816eabc1 - Reverting an accidental experimental test line commit. 2017-08-20 04:24:38 +00:00
Agetian
d28cbf3863 - Some Volrath's Shapeshifter fixes and QoL improvements. 2017-08-20 04:23:11 +00:00
swordshine
6269719b70 - Fixed last commit 2017-08-20 03:28:03 +00:00
swordshine
8a347e71ed - C17: Added Alms Collector 2017-08-20 03:26:18 +00:00
Agetian
9c44662f2e - Fixed a NPE in the new AiController code. 2017-08-20 03:24:25 +00:00
Agetian
884291a8a3 - Fixed a PO2 challenge deck title and description. 2017-08-20 03:23:15 +00:00
Agetian
97428909a4 - Minor code style fix. 2017-08-19 19:38:10 +00:00
Agetian
a0bb52ff5f - Added Volrath's Shapeshifter with rudimentary, simple AI support.
- Was tested in most typical circumstances, including cloning it. However, may not yet be perfect in some corner cases. Improvements are welcome.
2017-08-19 18:05:44 +00:00
Agetian
1a766c3ccc - Fixed Edgar Markov. 2017-08-19 13:06:39 +00:00
Agetian
19bea70dc0 - Added an implementation note. 2017-08-19 09:18:14 +00:00
Agetian
322a7020e3 - Better fix for Aluren: detect zone granting permissions from the MayPlay card options themselves, not by card name.
- Fixed Qasali Ambusher.
2017-08-19 09:15:09 +00:00
Agetian
242444ecc9 - Attempting to fix Aluren. 2017-08-19 07:31:42 +00:00
Agetian
4820ebba84 - Added AILogic$ ExileGraveyards to more cards. 2017-08-19 06:05:09 +00:00
Agetian
7658481db2 - Slightly improved AI for Scavenger Grounds activated ability. 2017-08-19 06:02:34 +00:00
Agetian
f41644bc97 - Fixed Crook of Condemnation mana cost. 2017-08-19 03:41:54 +00:00
Agetian
2163eb5910 - Fixed the AI playability of Bargain. 2017-08-18 19:26:09 +00:00
Agetian
ffbaf1e54f - Added Rock Hydra. 2017-08-18 19:02:16 +00:00
Agetian
76ac47bbc6 - Preparing Forge for Android publish 1.6.2.001 [incremental]. 2017-08-18 17:15:11 +00:00
Blacksmith
d499f1af65 Clear out release files in preparation for next release 2017-08-18 17:12:17 +00:00
Blacksmith
a249eeab22 [maven-release-plugin] prepare for next development iteration 2017-08-18 17:07:15 +00:00
Blacksmith
712e7dbeb8 [maven-release-plugin] prepare release forge-1.6.2 2017-08-18 17:07:05 +00:00
Blacksmith
22571acd0c Update README.txt for release 2017-08-18 17:05:06 +00:00
Agetian
eba8d0bd80 - Adding support for C17 cards to Planar Conquest [part 2]. 2017-08-18 17:03:07 +00:00
Agetian
4b52dea65a - Adding support for C17 cards to Planar Conquest. Some commanders can be moved to other planes later once they're implemented (e.g. Mirri to Dominaria, etc.). 2017-08-18 17:01:41 +00:00
Agetian
a5eeaae0d0 - Prevent a NPE when trying to zoom a card before a single card is selected. 2017-08-18 15:23:08 +00:00
Agetian
0d85eb6b90 - Documenting changes in CHANGES.txt. 2017-08-18 15:20:37 +00:00
Agetian
3abb226b5c - Added a keyboard shortcut to zoom in/out of the card in desktop Forge (default Z). 2017-08-18 14:53:20 +00:00
Agetian
d38fdfb291 - Documenting changes in CHANGES.txt. 2017-08-18 14:35:58 +00:00
Agetian
a63361302d - Migrating C17 from upcoming to named folders. 2017-08-18 14:34:54 +00:00
Agetian
069dbce000 - Removed a P/T assignment from Abandoned Sarcophagus. 2017-08-18 14:32:39 +00:00
Agetian
5970c575c8 - [C17] Added Mirri, Weatherlight Duelist.
- Not sure if oneBlockerPerOpponent is better off as a global rule or a keyword, feel free to change it to a keyworded implementation if appropriate.
2017-08-18 11:32:22 +00:00
Agetian
6ba659ff55 - Fixed the second, third, etc. AI never blocking anything in multiplayer when multiple players are attacked at the same time. 2017-08-18 06:22:55 +00:00
Agetian
2c9f0e3c7d - Fixed the AI incorrectly considering sorcery-speed tap abilities like Outlast, thus never using them. 2017-08-18 06:07:37 +00:00
Agetian
06359b9d06 - A clarification in CHANGES.txt. 2017-08-18 04:00:58 +00:00
Maxmtg
7f31fd5092 Split forge script handlign code 2017-08-17 21:10:06 +00:00
Agetian
01cdd82cc5 - Documenting changes in CHANGES.txt. 2017-08-17 17:13:59 +00:00
Agetian
c6094cf3ee - Documenting changes in CHANGES.txt. 2017-08-17 16:39:55 +00:00
Agetian
2ba7da0486 - Conspiracy: enabling Conspiracy draft sets in block definitions. 2017-08-17 16:33:28 +00:00
Agetian
dcc0295b4b - Conspiracy: adding CN2 rankings. 2017-08-17 16:28:58 +00:00
Agetian
8632b0ffec - Conspiracy: a little restructuring of chooseCardName in PlayerControllerAi. 2017-08-17 15:34:23 +00:00
Agetian
bc0b69857a - Conspiracy: The AI should not spam all the conspiracies with the same card name, instead choosing a new name every time. 2017-08-17 15:31:32 +00:00
Agetian
198d48fd84 - Conspiracy: allow the player to look at his own face-down conspiracies. Properly hide the opponent's conspiracies (including the chosen name). Make the AI properly add drafted conspiracies to the [Conspiracy] section of the deck. 2017-08-17 14:52:36 +00:00
Agetian
e85d6bebd0 - Shuffle the zones after the initVariantZones call. 2017-08-17 12:37:54 +00:00
Agetian
0679d9a30f - Conspiracy: fixed the AI never putting any conspiracies into the command zone. Added some simple logic for the AI revealing conspiracies when a spell with the chosen name was cast during any given turn. 2017-08-17 12:31:27 +00:00
Agetian
b0584d4499 - Fixed Hidden Agenda conspiracy cards crashing the game and their reveal ability not working. 2017-08-17 11:50:21 +00:00
Agetian
51bfc37a27 - Updated description to puzzle PS_AKH4. 2017-08-17 07:01:55 +00:00
Agetian
c979b27653 - Added description to puzzle PS_AKH4. 2017-08-17 07:01:26 +00:00
Agetian
184f0fad99 - Added puzzle PS_AKH7 coded by Rooger. 2017-08-17 06:38:14 +00:00
Agetian
f9b88c2738 - Removed a comment that doesn't apply anymore. 2017-08-17 06:35:38 +00:00
Agetian
f47a679dda - Allow precasting specific SAs from cards by their ordinal number (allows e.g. precasting planeswalker abilities).
- Added puzzle PS_AKH0 scripted by Rooger.
2017-08-17 06:34:55 +00:00
Agetian
e5e56b5260 - Allow precasting specific SAs from cards when setting up game state (needed by Puzzle Mode game states). 2017-08-17 06:06:44 +00:00
Agetian
c62f6b41d2 - A more correct interpretation of 901.6+901.10: the next player in turn order should initPlane in a planechase game if the planar controller leaves the game. 2017-08-17 05:37:33 +00:00
Agetian
6b32c4997d - Fixed Selesnya Loft Gardens. 2017-08-17 04:56:27 +00:00
Agetian
cb89c96dac - Momir Basic: when auto-generating the deck, only update the original deck instead of fully overwriting it. Allows the Momir Basic variant to be used in conjunction with other variants, such as Planechase, without crashing Forge. 2017-08-17 04:51:43 +00:00
Agetian
b31ca263fd - Implemented rule 901.6 for Planechase games. Should fix an issue with the planar deck cards disappearing when a player loses the game on his or her own turn. 2017-08-17 04:13:32 +00:00
Agetian
a0451cfab0 - Added 2 puzzles coded by Xitax. 2017-08-17 03:45:11 +00:00
Maxmtg
4baecd9f79 hasProperty of SpellAbility moved to ForgeScript 2017-08-16 21:38:26 +00:00
Maxmtg
381851aba7 moved player's hasProperty to ForgeScript 2017-08-16 20:36:30 +00:00
Maxmtg
23d3d5973b Move hasProperty of Card to ForgeScript 2017-08-16 20:10:26 +00:00
Agetian
fcdf9cf2ec - Do not run the replacement handler query if defender damage is determined to be 0. 2017-08-16 13:34:49 +00:00
Agetian
9831236d79 - Combat AI: Query the replacement handler for possible damage prevention when trying to determine if a blocker can destroy the attacker (fixes e.g. the AI chump blocking an exerted Oketra's Avenger even though all damage is to be prevented). 2017-08-16 13:32:30 +00:00
Agetian
c644d6bf2f - Removed a duplicated comment. 2017-08-16 12:26:41 +00:00
Agetian
d82b008c33 - Removed a testing useless reference. 2017-08-16 12:25:58 +00:00
Agetian
d53f9e99af - Fixed a miscalculation and related NPE when trying to predict the bonus from Arahbo, Roar of the World.
- Some extra NPE prevention measures in related trigger processing code.
2017-08-16 12:25:23 +00:00
Agetian
870b168cea - Disposal Mummy trigger is not optional. 2017-08-16 08:54:25 +00:00
Agetian
094e2478f9 - C17 rule update: tokens can phase out and phase in now (August 11, 2017 release notes). 2017-08-16 06:40:06 +00:00
Agetian
110b9f5058 - Removing some extra spaces in Commander 2017 edition file. 2017-08-16 06:10:40 +00:00
Agetian
d959753641 - A more generic check for LKI in ChangesZone Battlefield->Graveyard triggers (e.g. Dross Scorpion). 2017-08-16 06:08:46 +00:00
Agetian
027ecf29ae - Experimental: check LKI for the ChangesZone trigger of Dross Scorpion (fixes interaction with cards equipped with cards providing the Artifact type, e.g. Firemaw Kavu + Silversilk Armor). Should this be the default behavior? 2017-08-16 05:08:56 +00:00
Agetian
d4392635bf - Added 2 puzzles coded by Xitax. 2017-08-16 04:53:38 +00:00
Agetian
c72370eff0 - Removed an unused line from a test. 2017-08-16 03:48:18 +00:00
Agetian
6b799be7dd - Added support for PhasedOut to GameState. 2017-08-16 03:47:54 +00:00
Maxmtg
244d5bba47 Moved hasProperty code (it's forge script parsing in fact) out of CardState object to keep the class clean. 2017-08-15 21:40:03 +00:00
Agetian
d539ddf018 - Documenting changes in CHANGES.txt. 2017-08-15 19:28:25 +00:00
Agetian
f279792273 - AI should not overprioritize Gavony Township, otherwise it locks itself on mana too much. 2017-08-15 19:19:10 +00:00
Agetian
aa4a793566 - Added AILogic$ Fight to Ambuscade. 2017-08-15 17:41:20 +00:00
Agetian
1aaa2340f8 - Menace: do not display reminder text in game (an evergreen keyword that has appeared with no reminder text for a while). 2017-08-15 16:29:41 +00:00
Agetian
329247391b - Integrating Oracle updates by Indigo Dragon. 2017-08-15 16:10:51 +00:00
Agetian
3fc5daef01 - Corrected descriptions for Embalm and Eternalize. 2017-08-15 16:07:41 +00:00
Agetian
e087d04c17 - Basic AI logic for Shadow of the Grave. 2017-08-15 14:45:53 +00:00
Agetian
a3a713b608 - Preparing Forge for Android publish 1.6.1.005 [incremental]. 2017-08-15 13:45:45 +00:00
Agetian
98251a8631 - Added puzzle PC_090115 coded by Xitax. 2017-08-15 13:40:16 +00:00
Agetian
b147a16487 - Simplified implementation of LastRemembered. 2017-08-15 11:06:54 +00:00
Agetian
411b86197a - RememberedLast->LastRemembered for consistency with FirstRemembered (+ move the code closer to FirstRemembered). 2017-08-15 11:05:25 +00:00
Agetian
8acab6b662 - Minor formatting fix. 2017-08-15 11:00:21 +00:00
Agetian
dba550550c - Shifting Shadow: use the last remembered object as a reattachment target for compatibility with cards that remember other things too (e.g. Myr Welder). 2017-08-15 10:59:48 +00:00
Agetian
74cfc733dd - Removed code from the previous implementation of Shifting Shadow which is no longer needed. 2017-08-15 10:35:40 +00:00
Agetian
23981accd1 - A cleaner and hopefully sturdier implementation of Shifting Shadow that avoids the need for an intermediate effect card. 2017-08-15 10:32:37 +00:00
Agetian
209f34124e - Fixed Grasp of the Hieromancer. 2017-08-15 07:14:11 +00:00
Agetian
14cff4facf - Fixed Kothophed, Soul Hoarder trigger description. 2017-08-15 07:08:10 +00:00
Agetian
dac089b0fb - Added puzzle PC_082515 coded by Xitax. 2017-08-15 06:54:24 +00:00
Agetian
52aeda7faf - Avoid using a dedicated special parameter for Shifting Shadow spell description override. 2017-08-15 06:49:10 +00:00
Agetian
5273f5c9b6 - NPE protection measures. 2017-08-15 06:40:18 +00:00
Agetian
d2a30ea049 - [C17] Added Shifting Shadow.
- One of the hackier/more complicated scripts, so may not be perfect, though was tested in most situations, including stealing the enchanted creature.
2017-08-15 06:35:08 +00:00
Agetian
716ba3eea8 - Fixed a miscalculation in DamageAllAi. 2017-08-15 04:42:59 +00:00
Agetian
5cece53e50 - Fixed Madcap Experiment and Heirloom Blade "...the rest on the bottom of your library in a random order" clause. 2017-08-14 14:53:24 +00:00
Agetian
ba73f8a323 - ChangeZoneAi: do not activate the AF if it'll try to put a creature on the battlefield that will end up with toughness below zero after it enters the battlefield (Reassembling Skeleton + Elesh Norn, Grand Cenobite). 2017-08-14 14:13:14 +00:00
Agetian
81379a49a7 - A better variable name for Magus of the Mind. 2017-08-14 11:14:07 +00:00
Agetian
77e4862b3e - Minor formatting fix. 2017-08-14 11:01:59 +00:00
Agetian
1a3bd2aa74 - [C17] Added Magus of the Mind. 2017-08-14 11:01:36 +00:00
Agetian
9997b4a2de - A somewhat more comprehensive anti-cheating solution for Cavern of Souls AI. 2017-08-14 05:15:30 +00:00
Agetian
74b12606ef - Prevent the AI from cheating with Cavern of Souls, paying an arbitrary amount of mana with it. 2017-08-14 04:50:22 +00:00
Hanmac
14d5034b8e make Worms of the Earth into a GlobalRuleChange 2017-08-13 18:35:10 +00:00
Agetian
8b282818e9 - Fixed a couple NPEs in mobile Forge when canceling a Sealed/Draft match with a specific opponent. 2017-08-13 15:47:27 +00:00
Agetian
48a4f8ba67 - [C17] Removed the TODO entries from Curses since they use RepeatEach now, which should theoretically work for multiple attackers when that is implemented. 2017-08-13 15:18:59 +00:00
Agetian
1dd048eed4 - A little update to the previous commit. 2017-08-13 15:15:18 +00:00
Agetian
e398d6af75 - [C17] updated Curses to work correctly for "you and the triggered attacking player". 2017-08-13 15:13:32 +00:00
Agetian
39b1c1f7e4 - Removed a couple empty lines in new scripts. 2017-08-13 14:21:37 +00:00
Agetian
ff78b384ac - [C17] Added Izzet Chemister. 2017-08-13 14:10:35 +00:00
Agetian
d45a6f94f5 - [C17] Added Galecaster Colossues, Kindred Boon, Kindred Dominance. 2017-08-13 10:37:50 +00:00
Agetian
48fe8d5762 - Fixed a mistype in the C17 edition definition file. 2017-08-13 10:10:45 +00:00
Agetian
36b493f03a - Puzzle Mode: don't award Challenge achievements in this mode (take two). 2017-08-13 09:46:51 +00:00
Agetian
670ccaf891 - [C17] Added the 5 Curse cards. Currently there is no way to ensure that they work correctly with multiple attackers since Forge doesn't support shared turns yet. If support for shared turns is introduced, this should work correctly if TriggeredAttackingPlayer is expanded to return multiple attackers or if a RepeatEach construct is introduced iterating over an array of all attackers. 2017-08-13 09:40:18 +00:00
Maxmtg
d16a48a1a2 Move booster generation code to a separate package 2017-08-13 09:01:45 +00:00
Maxmtg
3f4eedbeab clean up more warnings about unused fields 2017-08-13 08:29:54 +00:00
Maxmtg
ef3dd4a833 Remove unused imports - to decrease number of warning from IDE. 2017-08-13 08:26:42 +00:00
Agetian
fee074db42 - Added a basic NeedsToPlayVar to other Pacts. 2017-08-13 07:19:57 +00:00
Agetian
5463af7f60 - Added a basic NeedsToPlayVar to Pact of the Titan. 2017-08-13 07:17:03 +00:00
Agetian
b776cd4a91 - Added a basic NeedsToPlayVar to Pact of Negation. 2017-08-13 03:55:33 +00:00
Maxmtg
45945e839f fix more warnings 2017-08-13 02:25:48 +00:00
Maxmtg
043ad7e3aa simplify ConquestAwardPool 2017-08-13 02:19:15 +00:00
Maxmtg
d04e186dea more unused methods and imports removal 2017-08-13 01:44:36 +00:00
Maxmtg
4253365e03 Moved AIOption to ai package, where it belongs 2017-08-13 00:53:26 +00:00
Maxmtg
07437b7880 Clean up unused imports that popped up in eclipse warnings List 2017-08-13 00:40:48 +00:00
Maxmtg
5ddd007f67 Remove player.getOpponent method, route former calls from AI through ComputerUtil.getOpponentFor(player) 2017-08-13 00:27:26 +00:00
Agetian
814978a178 - Fixed a NPE related to one of the previous commits. 2017-08-12 16:15:30 +00:00
Agetian
5350e77125 - When a permanent leaves the battlefield, remove all changed keywords on it (fixes e.g. the results of Magical Hack on Leviathan still partially persisting after it dies and is reanimated by something). 2017-08-12 14:56:22 +00:00
Agetian
22e2e32377 - Some subtype corrections. 2017-08-12 14:13:29 +00:00
Agetian
58b2c36498 - Added a comprehensive map of plural card subtypes to their singular counterparts, to the best of my knowledge of how the plural forms are formed (please take a look and update if necessary). Needed for the correct function of text change effects with any type (e.g. Homing Sliver + New Blood).
- Added an exception for the card text generation for the compound subtype "Eldrazi Scion" (e.g. Kor Castigator).
2017-08-12 13:58:26 +00:00
Agetian
0e4af7dbcf - Kess, Dissident Mage: don't remember the instant/sorcery card in case it was bounced from stack to a zone other than graveyard (e.g. to hand via Remand). 2017-08-12 12:37:52 +00:00
Agetian
3aba1c5ccf - Added NeedsToPlay AI var to Vizier of Many Faces. 2017-08-12 10:56:51 +00:00
Agetian
44af80f336 - [C17] Added O-Kagachi, Vengeful Kami. 2017-08-12 09:53:55 +00:00
Agetian
4591f5b372 - Updated Commander 2017 edition file. 2017-08-12 09:30:22 +00:00
Agetian
2270b2a224 - Mairsil AI: do not cage the same card twice. 2017-08-12 05:42:07 +00:00
Agetian
4eb68aae77 - [C17] Added Kess, Dissident Mage.
- May not be perfect in some corner cases (not sure), feel free to improve.
2017-08-12 05:34:16 +00:00
Agetian
c92e3cca6b - Fixed Watchers of the Dead exiling everything from its activator's graveyard. 2017-08-12 04:22:40 +00:00
Agetian
13ba0121d7 - Use isEmpty() to test an empty string. 2017-08-11 17:31:46 +00:00
Agetian
be4943a9d6 - Added rudimentary AI logic for Mairsil, the Pretender. 2017-08-11 17:23:58 +00:00
Agetian
9484e3b52d - Initial Commander 2017 edition file by Indigo Dragon. 2017-08-11 16:51:07 +00:00
Agetian
ef731703e8 - [C17] Added Fortunate Few. 2017-08-11 16:49:19 +00:00
Agetian
11b86806de - [C17] Added Taigam, Sidisi's Hand. 2017-08-11 15:35:24 +00:00
Agetian
b872f59a01 - Improved text for Path of Ancestry. 2017-08-11 14:24:01 +00:00
Agetian
b2fc1cc1f2 - [C17] Added Path of Ancestry.
- Fixed Command Tower and Path of Ancestry generating 2 colorless mana in non-EDH matches.
2017-08-11 14:14:57 +00:00
Agetian
6907c9c550 - Keeping num final in CharmEffect. 2017-08-11 09:46:13 +00:00
Agetian
93701585f8 - Somewhat improved Vindictive Lich (still doesn't play ball with hexproofed opponents though). 2017-08-11 09:40:59 +00:00
Agetian
50e596e63a - Mairsil, the Pretender: made the activations per turn limit parameter more generic. 2017-08-11 08:24:09 +00:00
Agetian
51ece30c34 - [C17] added Vindictive Lich.
- Might need an additional CharmEffect update in case the fact that you can choose multiple modes even with one opponent (but in which case the triggered ability fails and fizzles) is incorrect behavior [not sure].
2017-08-11 08:17:31 +00:00
Agetian
110a078074 - Cleaned up Mairsil a bit more. 2017-08-10 17:15:22 +00:00
Agetian
85b222d9e2 - Cosmetic reorder of target types in Mairsil. 2017-08-10 17:14:47 +00:00
Agetian
c974d4f30a - [C17] Added Mairsil, the Pretender. 2017-08-10 17:10:40 +00:00
Agetian
75916a21a6 - Removed an unused reference. 2017-08-10 16:24:03 +00:00
Agetian
8e2fc40105 - Updated Mathas, Fiend Seeker. 2017-08-10 16:23:01 +00:00
Agetian
f81bd857ed - Updated the text for Kindred Summons. 2017-08-10 16:04:19 +00:00
Agetian
e7ac05db75 - [C17] added 12 cards coded by Marek. 2017-08-10 16:03:20 +00:00
Agetian
eb3f526a96 - Fixed ChangeTextEffect with specific new text. 2017-08-10 15:47:04 +00:00
Agetian
371b0bdce1 - Committing card script corrections by Marek. 2017-08-10 13:48:11 +00:00
Maxmtg
06e70e7476 clean up some unused imports, add final modifier to parameter used in a closure 2017-08-10 13:05:41 +00:00
Agetian
a748fe6610 - Fixed Arahbo, Roar of the World. 2017-08-10 09:35:08 +00:00
Agetian
f43dd4d3dc - Some fixes for puzzle PC_072115 [part 3]. 2017-08-10 05:44:11 +00:00
Agetian
9c01b18922 - Some fixes for puzzle PC_072115 [part 2]. 2017-08-10 05:43:30 +00:00
Agetian
932fd032e8 - Some fixes for puzzle PC_072115. 2017-08-10 05:43:02 +00:00
Hanmac
c392deaf38 GameAction: checkStaticAbilities do CDA first 2017-08-10 04:59:07 +00:00
Hanmac
c575c1bea3 game state: counters are enum map, using hash map might break something 2017-08-10 04:34:22 +00:00
Agetian
38ec5efa32 - Puzzle PC_072115: marked Rhox Maulers as Renowned. 2017-08-10 04:12:31 +00:00
Agetian
70764e73c3 - Added three puzzles coded by Xitax. 2017-08-10 04:07:47 +00:00
Agetian
03fd7fba79 - [C17] added Fractured Identity. 2017-08-09 13:17:38 +00:00
Agetian
33d56a287b - Do not show an empty "Remembered:" tag on cards on which ClearRemembered was run. 2017-08-09 13:16:41 +00:00
Agetian
c3523f93aa - Reverted an occasional commit. 2017-08-09 12:52:01 +00:00
Agetian
26f08c7a30 - [C17] added 7 cards coded by Marek. 2017-08-09 12:51:21 +00:00
Agetian
a3266cda45 - Committing card script corrections from Marek. 2017-08-09 12:49:59 +00:00
Agetian
80052fabe8 - Minor formatting fix. 2017-08-09 10:20:43 +00:00
Agetian
3819a845f0 - Improved token pictures for tokens generated by Fabricate. 2017-08-09 10:12:34 +00:00
Agetian
d66c0f2d61 - Fixed AF DestroyAll destroying cards that can't be destroyed in case those cards were granted indestructibility by another card that is destroyed first (e.g. Crested Sunmare + horse tokens and an attempted removal via a Wrath of God type effect). 2017-08-09 09:55:25 +00:00
Agetian
0bf9da71ab - Fixed Traverse the Outlands casting cost. 2017-08-08 14:59:16 +00:00
Agetian
a07ff51c68 - [C17] Added 9 cards implemented by Marek (all tested ingame). 2017-08-08 12:23:56 +00:00
Agetian
48faabee49 - Committing card script corrections by Marek. 2017-08-08 11:38:27 +00:00
Agetian
d2f9eeab12 - Rudimentary prediction for Insult // Injury double damage effect for the AI (currently done in a way similar to how several other effects are predicted, which is (a) suboptimal - needs to be figured out from the replacement effect itself; (b) needs to be moved out from the Card and Player classes into the AI class, probably ComputerUtilCombat). Feel free to improve. 2017-08-08 10:00:04 +00:00
Agetian
c8ba4f0379 - Added AI logic parameter to Duskmantle, House of Shadow 2017-08-08 09:29:56 +00:00
Agetian
fba0fe1866 - Preparing Forge for Android publish 1.6.1.004 [incremental/fixes]. 2017-08-08 05:16:16 +00:00
Agetian
9685b92bc9 - Fixed a NPE in QuestDuelReader. 2017-08-08 04:46:48 +00:00
Agetian
3ae9779eec - Removed a debug print line. 2017-08-08 04:45:12 +00:00
Agetian
ebbdc46305 - Fix imports. 2017-08-08 04:44:37 +00:00
Agetian
4fb1c866da - AI: Avoid infinitely activating AF Untap on another permanent that will then be used to untap the first one (e.g. 2x Kiora's Follower) 2017-08-08 04:44:05 +00:00
Agetian
431d5ef8a8 - Added support for Renowned and Monstrous X properties to GameState. 2017-08-08 04:12:49 +00:00
Agetian
174d1f7838 - Puzzle mode improvements: no triggers will now run when the game state is set up; triggers will run in combat if attackers are declared. 2017-08-08 04:07:18 +00:00
Hanmac
b57ecea305 kefnet should cleanup choosen card 2017-08-07 16:28:23 +00:00
Agetian
64f39dcb79 - Fixed Glarecaster and Mirrorwood Treefolk. 2017-08-07 08:01:14 +00:00
Agetian
86149145f6 - Extended support for single target precast spells in Puzzle Mode. 2017-08-07 07:42:33 +00:00
Agetian
f3c41e9a66 - Use the ";" delimiter for precast spell declarations. 2017-08-07 05:00:42 +00:00
Agetian
2a6723f8db - Fixed an occasional change. 2017-08-07 04:50:12 +00:00
Agetian
2bb8d8b52a - Some puzzle mode improvements. Added support for precasting simple (untargeted) spells at the beginning of the game via AIPrecast/HumanPrecast parameters. Added support for Flipped and Meld card states. 2017-08-07 04:49:24 +00:00
Agetian
ac86cf19a0 - Added puzzles PC_080415 and PC_081115 coded by Xitax. 2017-08-07 03:59:08 +00:00
Agetian
ae29fef6d4 - Solution attempt #2 for the delayed trigger activator bug: store the original delayed trigger activator and restore it before running the trigger if a stored value was found, in case it was previously overwritten by the AI routines (fixes e.g. Rainbow Vale). 2017-08-07 03:43:38 +00:00
Agetian
54486cb5be - Reverting 34942 for now, causes weird, hard to track side effects. Better solution needed. 2017-08-06 18:17:56 +00:00
Agetian
22e41df8ea - Fixed a NPE in HostedMatch. 2017-08-06 18:03:45 +00:00
Agetian
d6dfc2ffb6 - Attempting to fix a long-standing bug with the delayed triggers getting the wrong activator set at their resolution time (the AI was aggressively overwriting the activator via its SpellAbility simulation routines).
- Ensured that all callers of getOriginalAndAltCostAbilities both call setActivatingPlayer and then reset it to its original value if there was one after the simulation completes. Thus, an aggressive setActivatingPlayer inside getOriginalAndAltCostAbilities should not be necessary.
2017-08-06 11:52:03 +00:00
Agetian
cb9d0ce3ed - Rebalanced Vanellope von Schweetz 1 quest opponent.
- Added Ice King 1 quest opponent by tojammot.
2017-08-06 11:43:11 +00:00
Agetian
cef5117e29 - Fixed Guan Yu, Sainted Warrior [fixing an occasionally missed | ]. 2017-08-06 07:43:56 +00:00
Agetian
466af94499 - Fixed Guan Yu, Sainted Warrior. 2017-08-06 07:43:17 +00:00
Agetian
ab9dea6930 - Fixed Challenge achievements being awarded in Puzzle Mode. 2017-08-05 11:50:54 +00:00
Agetian
cc2c585a55 - Added missing break statement. 2017-08-05 10:18:51 +00:00
Agetian
f9b1a59368 - Minor goal specification according to description. 2017-08-05 10:17:04 +00:00
Agetian
23cbe917c2 - Improvements and fixes for the new puzzle goal. 2017-08-05 10:09:04 +00:00
Hanmac
971f9694da ForgeConstants: add missing Constants 2017-08-05 10:01:05 +00:00
Agetian
32f057fa26 - Added support for "Destroy Permanents" goal in puzzle mode.
- Added puzzle PC_042815 implemented by Xitax.
2017-08-05 09:58:27 +00:00
Agetian
8c7edaaca4 - Added support for "Destroy Permanents"/"Kill Creatures" goal in puzzle mode.
- Added puzzle PC_042815 implemented by Xitax.
2017-08-05 09:54:27 +00:00
Hanmac
205323200a PlaneswalkerArchivements and AltWinArchivements are not make in res/lists 2017-08-05 09:45:33 +00:00
Agetian
9b880eb669 - Fixed AI for Jace, Telepath Unbound -3 ability. 2017-08-05 03:53:16 +00:00
austinio7116
3ce53cedc8 Something funny happened when I committed this file - so trying again 2017-08-04 19:15:23 +00:00
austinio7116
37e44968dc Update to card-based random deck data to include post HOU Pro Tour decks 2017-08-04 19:13:28 +00:00
Agetian
4fb58bc005 - All Hallow's Eve trigger should act as an intervening if clause. 2017-08-04 17:29:51 +00:00
Agetian
b24832dd84 - All Hallow's Eve must return the creatures to the battlefield once the last counter is removed (without necessarily waiting till the next upkeep). 2017-08-04 17:20:41 +00:00
Agetian
57aa32f0c1 - Added Bow to My Command.
- Currently probably one of the most complicated scripts, utilizing a couple hacks to simulate the "tap creatures with power 8 or greater" triggered ability. Most likely needs, at least, an UnlessCost update to support the tapXType cost with total creature power, and also potentially another update when/if shared turns and shared decisions are implemented.
2017-08-04 16:15:28 +00:00
Agetian
60c20cc628 - Added Choose Your Demise. 2017-08-04 15:57:05 +00:00
Agetian
eaae999878 - Fixed Jace, Architect of Thought -2 ability not allowing to order the cards going to the bottom of the library. 2017-08-04 15:54:31 +00:00
Agetian
f0af71d8c0 - Removed an experimental test line.
- Fixed Hazduhr the Abbot out of sight trigger definition.
2017-08-04 10:39:05 +00:00
Agetian
b65fed7a0d - Minor fix in description generation. 2017-08-04 10:23:45 +00:00
Agetian
364205cd9b - Added Hazduhr the Abbot. 2017-08-04 10:22:41 +00:00
Agetian
081ea769a1 - Removed an unused variable. 2017-08-04 08:46:48 +00:00
Agetian
7fef69635f - Chain of Acid doesn't need a special AITgts specification. 2017-08-04 08:46:01 +00:00
Agetian
e3c3315873 - Improved the AI logic for Chain of Acid. 2017-08-04 08:43:47 +00:00
Agetian
bb0218d3c6 - Fixed the AI cheat-activating AF PutCounter with a sac cost without sacrificing anything (e.g. Extruder). 2017-08-04 07:27:41 +00:00
Agetian
4e3e721c5a - Fixed Imaginary Threats. 2017-08-04 04:27:10 +00:00
Agetian
79321a0ffb - AlwaysPlayAi: implement an overriding confirmAction (set to return always true since AlwaysPlay is meant to do exactly that). 2017-08-03 19:52:48 +00:00
Agetian
230644141a - Removed a debug print line. 2017-08-03 19:40:29 +00:00
Agetian
480fa113b7 - Account for "schemes can't be set in motion this turn" when trying to set the scheme in motion again. 2017-08-03 19:39:44 +00:00
Agetian
9016d937a3 - Added My Laughter Echoes.
- Some improvements to AF SetInMotion which may be necessary to support Bow To My Command (seems almost scriptable, but the tap unless cost won't work).
2017-08-03 19:38:40 +00:00
Agetian
a361576f8a - Added A Reckoning Approaches. 2017-08-03 17:44:48 +00:00
Agetian
bee695afd4 - Removed unnecessary cleanup from My Forces Are Innumerable. 2017-08-03 16:36:41 +00:00
Agetian
f3fcdf808f - Added My Forces Are Innumerable (may need extension if E01 shared turns and shared opponent decisions are ever implemented in Forge). 2017-08-03 16:36:05 +00:00
Agetian
8e469493ac - Added Liege of the Hollows. 2017-08-03 16:03:38 +00:00
Agetian
872df9ee70 - Added Make Yourself Useful. 2017-08-03 15:05:34 +00:00
Agetian
69806f1260 - Added Errant Minion. 2017-08-03 14:35:50 +00:00
Agetian
0a24feab13 - Corrected Chain of Silence picture SVar. 2017-08-03 14:30:21 +00:00
Agetian
e40766583e - Added Every Dream a Nightmare. 2017-08-03 14:29:54 +00:00
Agetian
fd3fb5041b - Added Chain of Silence. 2017-08-03 13:56:30 +00:00
Agetian
8ffcaf328e - Added Delight in the Hunt. 2017-08-03 13:38:59 +00:00
Agetian
5381e54469 - Corrected Chain of Acid oracle text and picture SVar. 2017-08-03 13:32:45 +00:00
Agetian
f571685648 - Chain of Smog should be optional.
- Added Chain of Acid.
2017-08-03 13:29:14 +00:00
Agetian
701d363e3c - FIXME: correct the host card of a RepeatSubAbility since sometimes it's set incorrectly after an interaction, such as after a copy effect has been applied to a SA with Repeat/RepeatEach (e.g. the original Clone Legion not resolving correctly after its copy from Swarm Intelligence resolves). Couldn't figure out why this issue is happening, please assist if possible in determining the source of the problem and fixing it such that this workaround hack is unnecessary. 2017-08-03 13:00:06 +00:00
Agetian
afa697031c - A more comprehensive set of origin zones for Worms of the Earth replacement effect (probably superfluous, but accounts for potentially possible weird interactions). 2017-08-03 12:55:39 +00:00
Agetian
7239c98a4c - Worms of the Earth ruling: if a permanent spell tries to ETB (from stack) as a land, it goes into its owner's graveyard instead. 2017-08-03 12:54:09 +00:00
Agetian
48c0297fe7 - Fixed Abandoned Sarcophagus activating from zones other than the battlefield. 2017-08-02 19:14:41 +00:00
Agetian
a02be14f1a - Preparing Forge for Android publish 1.6.1.003 [incremental/fixes]. 2017-08-02 07:35:57 +00:00
Agetian
a0b8c758b4 - Fixed a crash in mobile Forge when trying to display the deck conformance error message outside of the Edt thread. 2017-08-02 07:31:52 +00:00
Agetian
97a8029f74 - Formatting fix. 2017-08-02 04:33:49 +00:00
Agetian
4d6781b26b - Added two new puzzles by Xitax and updated/fixed another one. 2017-08-02 04:33:23 +00:00
Agetian
b2d24725de - Simplify Worms of the Earth code. 2017-08-02 04:20:24 +00:00
Krazy
b04ac67860 Make booster quest pool generation respect starting pool set selections 2017-08-02 01:42:01 +00:00
Agetian
61784fd48d - Documenting changes in CHANGES.txt. 2017-08-01 18:22:59 +00:00
Agetian
f29738a96b - Added For Each of You, a Gift. 2017-08-01 17:36:15 +00:00
Agetian
cb313ed513 - Some text update in Worms of the Earth. 2017-08-01 17:22:25 +00:00
Agetian
e23656a2cd - Added Worms of the Earth. 2017-08-01 17:20:50 +00:00
Agetian
8a39ff469f - Added Power Leak (for now, needs a special exclusion in the AI code to properly determine who the paying player is, otherwise the AI believes that it's the opponent who owns Power Leak in case it's cast by the opponent). 2017-08-01 16:52:26 +00:00
Agetian
89c369189a - Added two puzzles by Rooger that are now scriptable. 2017-08-01 10:29:37 +00:00
Agetian
1108c92beb - Fixed the timing of the puzzle description popup in desktop Forge. 2017-08-01 10:25:09 +00:00
Agetian
e43cfce016 - Improved and fixed the handling of game states inside combat phases (DA, DB), added a way to mark cards that are attacking in saved game states. 2017-08-01 10:10:15 +00:00
Agetian
5931b53896 - Removed an unused variable. 2017-08-01 08:26:35 +00:00
Agetian
020b31cb6f - Added goal type "Survive" to puzzle mode.
- Added two new puzzles from Rooger that are now scriptable.
2017-08-01 06:37:05 +00:00
Agetian
8fc41c4c45 - Added several puzzles by Rooger that are now scriptable. 2017-08-01 06:21:44 +00:00
Agetian
d9b3c2c15c - Added a comment. 2017-08-01 06:17:44 +00:00
Agetian
10a9825f82 - Fixed a bug that caused multiple attachments on the same permanent not to work in game states.
- Improved the game state support to handle remembered cards and ExiledWith.
2017-08-01 06:16:46 +00:00
Agetian
2bbe168200 - Script execution in GameState: look for other scripts to execute if one of the SVars was not found. 2017-07-31 12:01:53 +00:00
Agetian
d6e8a96b19 - Script execution in GameState: do not iterate over all SVars, just grab the necessary SVar directly. 2017-07-31 12:00:02 +00:00
Agetian
c34fae302e - Added a way to force execution of a portion of card script when setting up game state (needed for some puzzles that utilize cards that dynamically set up effects when they ETB, e.g. Suspension Field).
- Added PC_041415 puzzle by Xitax.
2017-07-31 05:27:15 +00:00
Agetian
7957135979 - Some description fixes in puzzles. 2017-07-31 04:57:14 +00:00
Agetian
c9330d0b7f - Added Portal Second Age quest world by Xyx. 2017-07-31 04:01:59 +00:00
Agetian
9df5fc12cd Added PC_063015 puzzle by Xitax. 2017-07-31 03:43:37 +00:00
Sol
dd64685049 Fix typo in Oasis Ritualist 2017-07-30 23:58:06 +00:00
Hanmac
eb54b90001 blade of the bloodchief: add missing References 2017-07-30 20:04:04 +00:00
Agetian
b1ba5790cc - Added some puzzles implemented by Xitax. Updated the naming scheme for puzzles from GatheringMagic.com. 2017-07-30 15:02:49 +00:00
Agetian
56d79f36dc - Fixed a crash when loading a game state with a misspelled card name (will now report the card name in the console instead of hard-crashing). 2017-07-30 11:14:32 +00:00
Agetian
bbcf7b638f - Added a TODO entry to GameState. 2017-07-30 10:47:27 +00:00
Agetian
94cc314738 - Preserve ChosenColors and ChosenType in game states. 2017-07-30 10:40:32 +00:00
Agetian
39eb6482e3 - Handle marked damage before the triggers are unsuppressed when applying game states. 2017-07-30 10:19:46 +00:00
Agetian
85e66ebd8a - Fixed marking damage in puzzle mode and game states.
- Fixed card attachment in puzzle mode not working correctly when attaching a card to a card belonging to a different player.
2017-07-30 08:27:16 +00:00
Agetian
322b7084ea - Use calculateAmount in AttachAi instead of a custom method. 2017-07-30 06:53:52 +00:00
Agetian
8ac2c0462a - Fixed a NPE in AttachAi related to processing negative count for X (e.g. -X in Quag Sickness). 2017-07-30 06:40:17 +00:00
Hanmac
24e2e29894 CardFactoryUtil: fixed Eternalize and nonManaCost 2017-07-30 06:07:43 +00:00
Sol
e00aeb39e5 Fix Manalith rarity 2017-07-29 23:41:10 +00:00
Hanmac
6ec2849bd5 fixed god pharaoh's gift, the haste is only until end of turm 2017-07-29 19:56:18 +00:00
Hanmac
698c9d2923 PlayAi: fixed non-final error 2017-07-29 19:53:28 +00:00
Hanmac
82f379ecbe fixed gate to the afterlife 2017-07-29 17:23:14 +00:00
Agetian
a502ceea53 - Added a way to preserve marked damage in game states and puzzles. 2017-07-29 17:04:26 +00:00
Agetian
53e4b39066 - Prevent a NPE in booster foil generation when the template does not contain any slots. 2017-07-29 13:47:50 +00:00
Agetian
fceea79323 - Fixed interaction between put/remove counter as a part of cost payment and cards that trigger on things dying from counters (e.g. Necroskitter + a creature dying to a -1/-1 counter placed as a part of cost payment). 2017-07-29 13:36:09 +00:00
Agetian
6adf49de45 Fixed AILogic$ Evasion for EffectAi causing the AI to play the relevant cards out of context (e.g. Gruul Charm with the "can't block" mode on an empty battlefield). 2017-07-29 11:42:46 +00:00
Hanmac
0b85346ac4 TriggerHandler: try to fix Splendid Reclamation and Valakut 2017-07-29 05:11:22 +00:00
Agetian
53f0544da8 - Alternative Cost for spells should be added to all spells on the card, not only the first spell ability (fixes interaction with split cards, among possibly other things). 2017-07-28 13:45:14 +00:00
Agetian
839ced1b32 - Use a separate AI logic for "Detain target nonland permanent". 2017-07-28 07:44:02 +00:00
Agetian
b6fcbba57d - Preparing Forge for Android publish 1.6.1.002 [incremental/bug fixes]. 2017-07-28 04:24:43 +00:00
Agetian
b7f220d02b - Formatting fix. 2017-07-28 04:20:19 +00:00
Agetian
398ae8946c - Fixed the AI targeting nonland permanents with no activated abilities with effects like Detain. 2017-07-28 04:19:06 +00:00
Agetian
65357e1441 - Added PC_07 puzzle by Xitax. Differentiated between early and late Perplexing Chimera puzzles in their naming scheme. 2017-07-28 03:26:03 +00:00
Agetian
0a7f579bd8 - Fixed split cards having transform arrows in deck editor. 2017-07-27 19:23:50 +00:00
Agetian
2e0d2bb5e5 - Minor formatting fix. 2017-07-27 18:31:51 +00:00
Agetian
0612d447dc - Fixed generation of Wastes in OGW fat packs and an associated crash when trying to foil a non-existent Wastes in such a fat pack. 2017-07-27 18:28:37 +00:00
Agetian
fd7e19d339 - Fixed the unfoiling of cards displayed in booster boxes, fat packs, etc. (take two) 2017-07-27 17:54:59 +00:00
Agetian
7f8dec161d - Minor clarification for Puzzle Mode on mobile. 2017-07-27 05:55:38 +00:00
Agetian
26f50d109a - Show the puzzle description in a pop-up dialog window when the puzzle starts. 2017-07-27 04:29:28 +00:00
Agetian
acfdf23c22 - Fixed the player's max hand size in Puzzle Mode being equal to 0 by default (now set to 7 per the default MTG rules).
- Fixed the "Turns:X" parameter not working correctly in Puzzle Mode.
2017-07-26 12:58:04 +00:00
Agetian
a15588120b - Attempting to fix IndexOutOfBounds exception in GauntletWinLose 2017-07-26 04:18:13 +00:00
Agetian
da28816967 - Updating cards with AddPower/AddToughness$ -X 2017-07-25 19:17:44 +00:00
Agetian
53bf9922ca - Added several new puzzles by Xitax. 2017-07-25 16:06:01 +00:00
Agetian
363ff9610d - Some comment update. 2017-07-25 16:02:12 +00:00
Agetian
772c9bc77d - Updated the test case for Death's Shadow. 2017-07-25 15:51:47 +00:00
Agetian
c1f10c32c0 - Optimized SVars in Death's Shadow. 2017-07-25 15:49:54 +00:00
Agetian
0a8c36e086 - Fixed Death's Shadow implementation to work correctly with the new StaticAbilityContinuous modification. 2017-07-25 15:48:54 +00:00
Agetian
ae35e4b589 - Added a test case for Death's Shadow on negative life under the new rules.
- Some clarifications in recent tests.
2017-07-25 15:32:10 +00:00
Agetian
92a760541b - Fixed StaticAbilityContinuous applying negative P/T bonuses for cards like Death's Shadow when player's life was negative (incorrect under the new rules). 2017-07-25 15:13:14 +00:00
Agetian
3c67546f4a - Fixed Desert's Hold AI prioritizing wrong targets. 2017-07-25 14:57:55 +00:00
Agetian
695670cc96 - Fixed Wall of Forgotten Pharaohs generated description. 2017-07-25 14:43:08 +00:00
Agetian
0f42aa4ec7 - Fixed Mechanized Production AI targeting legendary artifacts to no value. 2017-07-25 11:54:01 +00:00
Agetian
8aca69f845 - Fixed the foil effect in boosters not "unfoiling" itself for multiple cards with the same name in the same card set (e.g. in a booster box). 2017-07-25 03:28:51 +00:00
Agetian
63be38a3c3 - Added an ability to show puzzle descriptions on the puzzle goal card. 2017-07-25 03:14:40 +00:00
Agetian
e7f6e5c740 - Fixed Hour of Devastation number of booster pictures. 2017-07-25 03:13:45 +00:00
Agetian
2ae8d49ec8 - Fixed "Auto Yield: Always No" in mobile Forge. 2017-07-25 03:13:11 +00:00
Agetian
f9e987e933 - Reverted several Java 8 functions to their Java 7 counterparts for Android compatibility. 2017-07-25 03:12:04 +00:00
Agetian
8e9c76a9e8 - Fixed Mummy Paramount (non-optional). 2017-07-25 03:10:17 +00:00
Agetian
ad52b60798 - Preparing Forge for Android publish 1.6.1.001 [incremental]. 2017-07-22 03:44:09 +00:00
Blacksmith
012cc28f8a Clear out release files in preparation for next release 2017-07-22 00:51:42 +00:00
Blacksmith
1668717cf8 [maven-release-plugin] prepare for next development iteration 2017-07-22 00:46:25 +00:00
16586 changed files with 460046 additions and 423005 deletions

254
.fbprefs
View File

@@ -1,127 +1,127 @@
#FindBugs User Preferences #FindBugs User Preferences
#Wed Jul 27 18:41:56 EDT 2011 #Wed Jul 27 18:41:56 EDT 2011
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
detectorBadAppletConstructor=BadAppletConstructor|false detectorBadAppletConstructor=BadAppletConstructor|false
detectorBadResultSetAccess=BadResultSetAccess|true detectorBadResultSetAccess=BadResultSetAccess|true
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
detectorBadUseOfReturnValue=BadUseOfReturnValue|true detectorBadUseOfReturnValue=BadUseOfReturnValue|true
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
detectorBooleanReturnNull=BooleanReturnNull|true detectorBooleanReturnNull=BooleanReturnNull|true
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
detectorCheckTypeQualifiers=CheckTypeQualifiers|true detectorCheckTypeQualifiers=CheckTypeQualifiers|true
detectorCloneIdiom=CloneIdiom|true detectorCloneIdiom=CloneIdiom|true
detectorComparatorIdiom=ComparatorIdiom|true detectorComparatorIdiom=ComparatorIdiom|true
detectorConfusedInheritance=ConfusedInheritance|true detectorConfusedInheritance=ConfusedInheritance|true
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
detectorCrossSiteScripting=CrossSiteScripting|true detectorCrossSiteScripting=CrossSiteScripting|true
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
detectorDontUseEnum=DontUseEnum|true detectorDontUseEnum=DontUseEnum|true
detectorDroppedException=DroppedException|true detectorDroppedException=DroppedException|true
detectorDumbMethodInvocations=DumbMethodInvocations|true detectorDumbMethodInvocations=DumbMethodInvocations|true
detectorDumbMethods=DumbMethods|true detectorDumbMethods=DumbMethods|true
detectorDuplicateBranches=DuplicateBranches|true detectorDuplicateBranches=DuplicateBranches|true
detectorEmptyZipFileEntry=EmptyZipFileEntry|true detectorEmptyZipFileEntry=EmptyZipFileEntry|true
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
detectorFinalizerNullsFields=FinalizerNullsFields|true detectorFinalizerNullsFields=FinalizerNullsFields|true
detectorFindBadCast2=FindBadCast2|true detectorFindBadCast2=FindBadCast2|true
detectorFindBadForLoop=FindBadForLoop|true detectorFindBadForLoop=FindBadForLoop|true
detectorFindCircularDependencies=FindCircularDependencies|false detectorFindCircularDependencies=FindCircularDependencies|false
detectorFindDeadLocalStores=FindDeadLocalStores|true detectorFindDeadLocalStores=FindDeadLocalStores|true
detectorFindDoubleCheck=FindDoubleCheck|true detectorFindDoubleCheck=FindDoubleCheck|true
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
detectorFindFinalizeInvocations=FindFinalizeInvocations|true detectorFindFinalizeInvocations=FindFinalizeInvocations|true
detectorFindFloatEquality=FindFloatEquality|true detectorFindFloatEquality=FindFloatEquality|true
detectorFindHEmismatch=FindHEmismatch|true detectorFindHEmismatch=FindHEmismatch|true
detectorFindInconsistentSync2=FindInconsistentSync2|true detectorFindInconsistentSync2=FindInconsistentSync2|true
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
detectorFindMaskedFields=FindMaskedFields|true detectorFindMaskedFields=FindMaskedFields|true
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
detectorFindNakedNotify=FindNakedNotify|true detectorFindNakedNotify=FindNakedNotify|true
detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
detectorFindNonShortCircuit=FindNonShortCircuit|true detectorFindNonShortCircuit=FindNonShortCircuit|true
detectorFindNullDeref=FindNullDeref|true detectorFindNullDeref=FindNullDeref|true
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
detectorFindOpenStream=FindOpenStream|true detectorFindOpenStream=FindOpenStream|true
detectorFindPuzzlers=FindPuzzlers|true detectorFindPuzzlers=FindPuzzlers|true
detectorFindRefComparison=FindRefComparison|true detectorFindRefComparison=FindRefComparison|true
detectorFindReturnRef=FindReturnRef|true detectorFindReturnRef=FindReturnRef|true
detectorFindRunInvocations=FindRunInvocations|true detectorFindRunInvocations=FindRunInvocations|true
detectorFindSelfComparison=FindSelfComparison|true detectorFindSelfComparison=FindSelfComparison|true
detectorFindSelfComparison2=FindSelfComparison2|true detectorFindSelfComparison2=FindSelfComparison2|true
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
detectorFindSpinLoop=FindSpinLoop|true detectorFindSpinLoop=FindSpinLoop|true
detectorFindSqlInjection=FindSqlInjection|true detectorFindSqlInjection=FindSqlInjection|true
detectorFindTwoLockWait=FindTwoLockWait|true detectorFindTwoLockWait=FindTwoLockWait|true
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
detectorFindUnconditionalWait=FindUnconditionalWait|true detectorFindUnconditionalWait=FindUnconditionalWait|true
detectorFindUninitializedGet=FindUninitializedGet|true detectorFindUninitializedGet=FindUninitializedGet|true
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
detectorFindUnreleasedLock=FindUnreleasedLock|true detectorFindUnreleasedLock=FindUnreleasedLock|true
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
detectorFindUnsyncGet=FindUnsyncGet|true detectorFindUnsyncGet=FindUnsyncGet|true
detectorFindUselessControlFlow=FindUselessControlFlow|true detectorFindUselessControlFlow=FindUselessControlFlow|true
detectorFormatStringChecker=FormatStringChecker|true detectorFormatStringChecker=FormatStringChecker|true
detectorHugeSharedStringConstants=HugeSharedStringConstants|true detectorHugeSharedStringConstants=HugeSharedStringConstants|true
detectorIDivResultCastToDouble=IDivResultCastToDouble|true detectorIDivResultCastToDouble=IDivResultCastToDouble|true
detectorIncompatMask=IncompatMask|true detectorIncompatMask=IncompatMask|true
detectorInconsistentAnnotations=InconsistentAnnotations|true detectorInconsistentAnnotations=InconsistentAnnotations|true
detectorInefficientMemberAccess=InefficientMemberAccess|false detectorInefficientMemberAccess=InefficientMemberAccess|false
detectorInefficientToArray=InefficientToArray|true detectorInefficientToArray=InefficientToArray|true
detectorInfiniteLoop=InfiniteLoop|true detectorInfiniteLoop=InfiniteLoop|true
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
detectorInitializationChain=InitializationChain|true detectorInitializationChain=InitializationChain|true
detectorInstantiateStaticClass=InstantiateStaticClass|true detectorInstantiateStaticClass=InstantiateStaticClass|true
detectorInvalidJUnitTest=InvalidJUnitTest|true detectorInvalidJUnitTest=InvalidJUnitTest|true
detectorIteratorIdioms=IteratorIdioms|true detectorIteratorIdioms=IteratorIdioms|true
detectorLazyInit=LazyInit|true detectorLazyInit=LazyInit|true
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
detectorMethodReturnCheck=MethodReturnCheck|true detectorMethodReturnCheck=MethodReturnCheck|true
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
detectorMutableLock=MutableLock|true detectorMutableLock=MutableLock|true
detectorMutableStaticFields=MutableStaticFields|true detectorMutableStaticFields=MutableStaticFields|true
detectorNaming=Naming|true detectorNaming=Naming|true
detectorNumberConstructor=NumberConstructor|true detectorNumberConstructor=NumberConstructor|true
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
detectorPublicSemaphores=PublicSemaphores|false detectorPublicSemaphores=PublicSemaphores|false
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
detectorRedundantInterfaces=RedundantInterfaces|true detectorRedundantInterfaces=RedundantInterfaces|true
detectorRepeatedConditionals=RepeatedConditionals|true detectorRepeatedConditionals=RepeatedConditionals|true
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
detectorSerializableIdiom=SerializableIdiom|true detectorSerializableIdiom=SerializableIdiom|true
detectorStartInConstructor=StartInConstructor|true detectorStartInConstructor=StartInConstructor|true
detectorStaticCalendarDetector=StaticCalendarDetector|true detectorStaticCalendarDetector=StaticCalendarDetector|true
detectorStringConcatenation=StringConcatenation|true detectorStringConcatenation=StringConcatenation|true
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
detectorSwitchFallthrough=SwitchFallthrough|true detectorSwitchFallthrough=SwitchFallthrough|true
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
detectorURLProblems=URLProblems|true detectorURLProblems=URLProblems|true
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
detectorUnnecessaryMath=UnnecessaryMath|true detectorUnnecessaryMath=UnnecessaryMath|true
detectorUnreadFields=UnreadFields|true detectorUnreadFields=UnreadFields|true
detectorUseObjectEquals=UseObjectEquals|false detectorUseObjectEquals=UseObjectEquals|false
detectorUselessSubclassMethod=UselessSubclassMethod|false detectorUselessSubclassMethod=UselessSubclassMethod|false
detectorVarArgsProblems=VarArgsProblems|true detectorVarArgsProblems=VarArgsProblems|true
detectorVolatileUsage=VolatileUsage|true detectorVolatileUsage=VolatileUsage|true
detectorWaitInLoop=WaitInLoop|true detectorWaitInLoop=WaitInLoop|true
detectorWrongMapIterator=WrongMapIterator|true detectorWrongMapIterator=WrongMapIterator|true
detectorXMLFactoryBypass=XMLFactoryBypass|true detectorXMLFactoryBypass=XMLFactoryBypass|true
detector_threshold=2 detector_threshold=2
effort=default effort=default
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL| filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
run_at_full_build=false run_at_full_build=false

21682
.gitattributes vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,284 +1,284 @@
eclipse.preferences.version=1 eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7 org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7 org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0 org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0 org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1 org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0 org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
org.eclipse.jdt.core.formatter.comment.line_length=80 org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2 org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
org.eclipse.jdt.core.formatter.indentation.size=4 org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=true org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=120 org.eclipse.jdt.core.formatter.lineSplit=120
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=space org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=4 org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.use_on_off_tags=false org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true

File diff suppressed because one or more lines are too long

21
checkstyle.xml Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
<!--
Checkstyle is very configurable.
http://checkstyle.sf.net (or in your downloaded distribution).
-->
<module name="Checker">
<module name="TreeWalker">
<module name="RedundantImport"/>
<module name="UnusedImports">
<!-- <property name="processJavadoc" value="false"/> -->
</module>
</module>
</module>

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<classpath> <classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/> <classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/> <classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/> <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/> <classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/> <classpathentry combineaccessrules="false" kind="src" path="/forge-core"/>
</classpath> <classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -3,6 +3,7 @@
<name>forge-ai</name> <name>forge-ai</name>
<comment></comment> <comment></comment>
<projects> <projects>
<project>forge-game</project>
</projects> </projects>
<buildSpec> <buildSpec>
<buildCommand> <buildCommand>

View File

@@ -1,4 +1,3 @@
eclipse.preferences.version=1 eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1 encoding//src/main/java=ISO-8859-1
encoding//src/test/java=ISO-8859-1 encoding/<project>=UTF-8
encoding/<project>=UTF-8

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.1</version> <version>1.6.16</version>
</parent> </parent>
<artifactId>forge-ai</artifactId> <artifactId>forge-ai</artifactId>
@@ -29,4 +29,31 @@
<version>3.6.1</version> <version>3.6.1</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>checkstyle-validation</id>
<phase>validate</phase>
<configuration>
<configLocation>../checkstyle.xml</configLocation>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<failOnViolation>true</failOnViolation>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

View File

@@ -1,4 +1,4 @@
package forge; package forge.ai;
public enum AIOption { public enum AIOption {
USE_SIMULATION; USE_SIMULATION;

View File

@@ -17,14 +17,9 @@
*/ */
package forge.ai; package forge.ai;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.ability.AnimateAi; import forge.ai.ability.AnimateAi;
import forge.card.CardTypeView; import forge.card.CardTypeView;
import forge.game.GameEntity; import forge.game.GameEntity;
@@ -35,14 +30,21 @@ import forge.game.card.*;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.combat.GlobalAttackRestrictions; import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Expressions;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import java.util.ArrayList;
import java.util.List;
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer() //doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
/** /**
@@ -59,9 +61,6 @@ public class AiAttackController {
private final List<Card> attackers; private final List<Card> attackers;
private final List<Card> blockers; private final List<Card> blockers;
private final static Random random = new Random();
private final static int randomInt = random.nextInt();
private List<Card> oppList; // holds human player creatures private List<Card> oppList; // holds human player creatures
private List<Card> myList; // holds computer creatures private List<Card> myList; // holds computer creatures
@@ -104,7 +103,7 @@ public class AiAttackController {
} // overloaded constructor to evaluate single specified attacker } // overloaded constructor to evaluate single specified attacker
public static List<Card> getOpponentCreatures(final Player defender) { public static List<Card> getOpponentCreatures(final Player defender) {
List<Card> defenders = Lists.newArrayList(); List<Card> defenders = new ArrayList<Card>();
defenders.addAll(defender.getCreaturesInPlay()); defenders.addAll(defender.getCreaturesInPlay());
Predicate<Card> canAnimate = new Predicate<Card>() { Predicate<Card> canAnimate = new Predicate<Card>() {
@Override @Override
@@ -203,8 +202,11 @@ public class AiAttackController {
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) { if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
return true; return true;
} }
if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted")
&& ComputerUtilCombat.predictDamageTo(opp, 1, attacker, true) > 0) { // TODO check if that makes sense
int exalted = ai.countExaltedBonus();
if (this.attackers.size() == 1 && exalted > 0
&& ComputerUtilCombat.predictDamageTo(opp, exalted, attacker, true) > 0) {
return true; return true;
} }
@@ -243,6 +245,19 @@ public class AiAttackController {
return false; return false;
} }
public final static Card getCardCanBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
final List<Card> attackerList = new ArrayList<Card>(attackers);
if (!c.isCreature()) {
return null;
}
for (final Card attacker : attackerList) {
if (CombatUtil.canBlock(attacker, c, nextTurn)) {
return attacker;
}
}
return null;
}
// this checks to make sure that the computer player doesn't lose when the human player attacks // this checks to make sure that the computer player doesn't lose when the human player attacks
// this method is used by getAttackers() // this method is used by getAttackers()
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) { public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
@@ -253,6 +268,40 @@ public class AiAttackController {
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) { if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
return attackers; return attackers;
} }
// no need to block (already holding mana to cast fog next turn)
if (!AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
// Don't send the card that'll do the fog effect to attack, it's unsafe!
List<Card> toRemove = Lists.newArrayList();
for(Card c : attackers) {
if (AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
toRemove.add(c);
}
}
attackers.removeAll(toRemove);
return attackers;
}
// no need to block if an effect is in play which untaps all creatures (pseudo-Vigilance akin to
// Awakening or Prophet of Kruphix)
for (Card card : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
boolean untapsEachTurn = card.hasSVar("UntapsEachTurn");
boolean untapsEachOtherTurn = card.hasSVar("UntapsEachOtherPlayerTurn");
if (untapsEachTurn || untapsEachOtherTurn) {
String affected = untapsEachTurn ? card.getSVar("UntapsEachTurn")
: card.getSVar("UntapsEachOtherPlayerTurn");
for (String aff : TextUtil.split(affected, ',')) {
if (aff.equals("Creature")
&& (untapsEachTurn || (untapsEachOtherTurn && ai.equals(card.getController())))) {
return attackers;
}
}
}
}
List<Card> opponentsAttackers = new ArrayList<Card>(oppList); List<Card> opponentsAttackers = new ArrayList<Card>(oppList);
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() { opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
@Override @Override
@@ -271,7 +320,7 @@ public class AiAttackController {
} }
continue; continue;
} }
if (c.hasKeyword("Vigilance")) { if (c.hasKeyword(Keyword.VIGILANCE)) {
vigilantes.add(c); vigilantes.add(c);
notNeededAsBlockers.remove(c); // they will be re-added later notNeededAsBlockers.remove(c); // they will be re-added later
if (canBlockAnAttacker(c, opponentsAttackers, false)) { if (canBlockAnAttacker(c, opponentsAttackers, false)) {
@@ -313,7 +362,7 @@ public class AiAttackController {
// In addition, if the computer guesses it needs no blockers, make sure // In addition, if the computer guesses it needs no blockers, make sure
// that // that
// it won't be surprised by Exalted // it won't be surprised by Exalted
final int humanExaltedBonus = countExaltedBonus(opp); final int humanExaltedBonus = opp.countExaltedBonus();
if (humanExaltedBonus > 0) { if (humanExaltedBonus > 0) {
final boolean finestHour = opp.isCardInPlay("Finest Hour"); final boolean finestHour = opp.isCardInPlay("Finest Hour");
@@ -362,8 +411,12 @@ public class AiAttackController {
blockersLeft--; blockersLeft--;
continue; continue;
} }
totalAttack += ComputerUtilCombat.damageIfUnblocked(attacker, ai, null, false);
totalPoison += ComputerUtilCombat.poisonIfUnblocked(attacker, ai); // Test for some special triggers that can change the creature in combat
Card effectiveAttacker = ComputerUtilCombat.applyPotentialAttackCloneTriggers(attacker);
totalAttack += ComputerUtilCombat.damageIfUnblocked(effectiveAttacker, ai, null, false);
totalPoison += ComputerUtilCombat.poisonIfUnblocked(effectiveAttacker, ai);
} }
if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) { if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) {
@@ -402,7 +455,7 @@ public class AiAttackController {
CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield); CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield);
if (c.getName().equals("Heart of Kiran")) { if (c.getName().equals("Heart of Kiran")) {
if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANEWALKERS).isEmpty()) { if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANESWALKERS).isEmpty()) {
// can be activated by removing a loyalty counter instead of tapping a creature // can be activated by removing a loyalty counter instead of tapping a creature
continue; continue;
} }
@@ -424,16 +477,45 @@ public class AiAttackController {
final Player opp = this.defendingOpponent; final Player opp = this.defendingOpponent;
for (Card attacker : attackers) { // if true, the AI will attempt to identify which blockers will already be taken,
if (!CombatUtil.canBeBlocked(attacker, this.blockers, null) // thus attempting to predict how many creatures with evasion can actively block
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { boolean predictEvasion = false;
unblockedAttackers.add(attacker); if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
if (aic.getBooleanProperty(AiProps.COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION)) {
predictEvasion = true;
} }
} }
CardCollection accountedBlockers = new CardCollection(this.blockers);
CardCollection categorizedAttackers = new CardCollection();
if (predictEvasion) {
// split categorizedAttackers such that the ones with evasion come first and
// can be properly accounted for. Note that at this point the attackers need
// to be sorted by power already (see the Collections.sort call above).
categorizedAttackers.addAll(ComputerUtilCombat.categorizeAttackersByEvasion(this.attackers));
} else {
categorizedAttackers.addAll(this.attackers);
}
for (Card attacker : categorizedAttackers) {
if (!CombatUtil.canBeBlocked(attacker, accountedBlockers, null)
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
unblockedAttackers.add(attacker);
} else {
if (predictEvasion) {
List<Card> potentialBestBlockers = CombatUtil.getPotentialBestBlockers(attacker, accountedBlockers, null);
accountedBlockers.removeAll(potentialBestBlockers);
}
}
}
remainingAttackers.removeAll(unblockedAttackers);
for (Card blocker : this.blockers) { for (Card blocker : this.blockers) {
if (blocker.hasKeyword("CARDNAME can block any number of creatures.") if (blocker.hasKeyword("CARDNAME can block any number of creatures.")
|| blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures.")) { || blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures each combat.")) {
for (Card attacker : this.attackers) { for (Card attacker : this.attackers) {
if (CombatUtil.canBlock(attacker, blocker)) { if (CombatUtil.canBlock(attacker, blocker)) {
remainingAttackers.remove(attacker); remainingAttackers.remove(attacker);
@@ -449,7 +531,7 @@ public class AiAttackController {
if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) { if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) {
break; break;
} }
if (blocker.hasKeyword("CARDNAME can block an additional creature.")) { if (blocker.hasKeyword("CARDNAME can block an additional creature each combat.")) {
blockedAttackers.add(remainingAttackers.get(0)); blockedAttackers.add(remainingAttackers.get(0));
remainingAttackers.remove(0); remainingAttackers.remove(0);
maxBlockersAfterCrew--; maxBlockersAfterCrew--;
@@ -465,7 +547,7 @@ public class AiAttackController {
int trampleDamage = 0; int trampleDamage = 0;
for (Card attacker : blockedAttackers) { for (Card attacker : blockedAttackers) {
if (attacker.hasKeyword("Trample")) { if (attacker.hasKeyword(Keyword.TRAMPLE)) {
int damage = ComputerUtilCombat.getAttack(attacker); int damage = ComputerUtilCombat.getAttack(attacker);
for (Card blocker : this.blockers) { for (Card blocker : this.blockers) {
if (CombatUtil.canBlock(attacker, blocker)) { if (CombatUtil.canBlock(attacker, blocker)) {
@@ -478,13 +560,15 @@ public class AiAttackController {
} }
} }
if (ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + ComputerUtil.possibleNonCombatDamage(ai) int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
+ trampleDamage >= opp.getLife() int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife()
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) { && !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
return true; return true;
} }
if (ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp) >= 10 - opp.getPoisonCounters()) { if (totalPoisonDamage >= 10 - opp.getPoisonCounters()) {
return true; return true;
} }
@@ -498,11 +582,16 @@ public class AiAttackController {
} }
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0)); Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0));
final GameEntity entity = ai.getMustAttackEntity(); // Attempt to see if there's a defined entity that must be attacked strictly this turn...
GameEntity entity = ai.getMustAttackEntityThisTurn();
if (entity == null) {
// ...or during the attacking creature controller's turn
entity = ai.getMustAttackEntity();
}
if (null != entity) { if (null != entity) {
int n = defs.indexOf(entity); int n = defs.indexOf(entity);
if (-1 == n) { if (-1 == n) {
System.out.println("getMustAttackEntity() returned something not in defenders."); System.out.println("getMustAttackEntity() or getMustAttackEntityThisTurn() returned something not in defenders.");
return prefDefender; return prefDefender;
} else { } else {
return entity; return entity;
@@ -532,22 +621,26 @@ public class AiAttackController {
* @return a {@link forge.game.combat.Combat} object. * @return a {@link forge.game.combat.Combat} object.
*/ */
public final void declareAttackers(final Combat combat) { public final void declareAttackers(final Combat combat) {
// if this method is called multiple times during a turn,
// it will always return the same value
// randomInt is used so that the computer doesn't always
// do the same thing on turn 3 if he had the same creatures in play
// I know this is a little confusing
random.setSeed(ai.getGame().getPhaseHandler().getTurn() + AiAttackController.randomInt);
if (this.attackers.isEmpty()) { if (this.attackers.isEmpty()) {
return; return;
} }
// Aggro options
boolean playAggro = false; boolean playAggro = false;
int chanceToAttackToTrade = 0;
boolean tradeIfTappedOut = false;
int extraChanceIfOppHasMana = 0;
boolean tradeIfLowerLifePressure = false;
if (ai.getController().isAI()) { if (ai.getController().isAI()) {
playAggro = ((PlayerControllerAi) ai.getController()).getAi().getProperty(AiProps.PLAY_AGGRO).equals("true"); AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
} }
final boolean bAssault = this.doAssault(ai); final boolean bAssault = this.doAssault(ai);
// TODO: detect Lightmine Field by presence of a card with a specific trigger // TODO: detect Lightmine Field by presence of a card with a specific trigger
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field"); final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
@@ -558,7 +651,11 @@ public class AiAttackController {
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions? // TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders()); 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) { if (attackMax == 0) {
// can't attack anymore // can't attack anymore
@@ -581,7 +678,8 @@ public class AiAttackController {
&& isEffectiveAttacker(ai, attacker, combat)) { && isEffectiveAttacker(ai, attacker, combat)) {
mustAttack = true; mustAttack = true;
} else { } else {
for (String s : attacker.getKeywords()) { for (KeywordInterface inst : attacker.getKeywords()) {
String s = inst.getOriginal();
if (s.equals("CARDNAME attacks each turn if able.") if (s.equals("CARDNAME attacks each turn if able.")
|| s.startsWith("CARDNAME attacks specific player each combat if able") || s.startsWith("CARDNAME attacks specific player each combat if able")
|| s.equals("CARDNAME attacks each combat if able.")) { || s.equals("CARDNAME attacks each combat if able.")) {
@@ -590,7 +688,7 @@ public class AiAttackController {
} }
} }
} }
if (mustAttack || attacker.getController().getMustAttackEntity() != null) { if (mustAttack || attacker.getController().getMustAttackEntity() != null || attacker.getController().getMustAttackEntityThisTurn() != null) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
attackersLeft.remove(attacker); attackersLeft.remove(attacker);
numForcedAttackers++; numForcedAttackers++;
@@ -635,20 +733,15 @@ public class AiAttackController {
// Exalted // Exalted
if (combat.getAttackers().isEmpty()) { if (combat.getAttackers().isEmpty()) {
boolean exalted = false; boolean exalted = ai.countExaltedBonus() > 2;
int exaltedCount = 0;
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) { if (!exalted) {
if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) { for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
exalted = true; if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) {
break; exalted = true;
} break;
if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) { }
exalted = true; if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
break;
}
if (c.hasKeyword("Exalted")) {
exaltedCount++;
if (exaltedCount > 2) {
exalted = true; exalted = true;
break; break;
} }
@@ -711,7 +804,20 @@ public class AiAttackController {
} }
} }
for (final Card pCard : this.oppList) { boolean predictEvasion = (ai.getController().isAI()
&& ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION));
CardCollection categorizedOppList = new CardCollection();
if (predictEvasion) {
// If predicting evasion, make sure that attackers with evasion are considered first
// (to avoid situations where the AI would predict his non-flyers to be blocked with
// flying creatures and then believe that flyers will necessarily be left unblocked)
categorizedOppList.addAll(ComputerUtilCombat.categorizeAttackersByEvasion(this.oppList));
} else {
categorizedOppList.addAll(this.oppList);
}
for (final Card pCard : categorizedOppList) {
// if the creature can attack next turn add it to counter attackers list // if the creature can attack next turn add it to counter attackers list
if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) { if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) {
nextTurnAttackers.add(pCard); nextTurnAttackers.add(pCard);
@@ -719,8 +825,13 @@ public class AiAttackController {
humanForces += 1; // player forces they might use to attack humanForces += 1; // player forces they might use to attack
} }
// increment player forces that are relevant to an attritional attack - includes walls // increment player forces that are relevant to an attritional attack - includes walls
if (canBlockAnAttacker(pCard, candidateAttackers, true)) {
Card potentialOppBlocker = getCardCanBlockAnAttacker(pCard, candidateAttackers, true);
if (potentialOppBlocker != null) {
humanForcesForAttritionalAttack += 1; humanForcesForAttritionalAttack += 1;
if (predictEvasion) {
candidateAttackers.remove(potentialOppBlocker);
}
} }
} }
@@ -847,8 +958,18 @@ public class AiAttackController {
if (ratioDiff > 0 && doAttritionalAttack) { if (ratioDiff > 0 && doAttritionalAttack) {
this.aiAggression = 5; // attack at all costs this.aiAggression = 5; // attack at all costs
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0)) } else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
|| (playAggro && humanLifeToDamageRatio > 1)) { || (playAggro && MyRandom.percentTrue(chanceToAttackToTrade) && humanLifeToDamageRatio > 1)) {
this.aiAggression = 4; // attack expecting to trade or damage player. this.aiAggression = 4; // attack expecting to trade or damage player.
} else if (MyRandom.percentTrue(chanceToAttackToTrade) && humanLifeToDamageRatio > 1
&& defendingOpponent != null
&& ComputerUtil.countUsefulCreatures(ai) > ComputerUtil.countUsefulCreatures(defendingOpponent)
&& ai.getLife() > defendingOpponent.getLife()
&& !ComputerUtilCombat.lifeInDanger(ai, combat)
&& (ComputerUtilMana.getAvailableManaEstimate(ai) > 0) || tradeIfTappedOut
&& (ComputerUtilMana.getAvailableManaEstimate(defendingOpponent) == 0) || MyRandom.percentTrue(extraChanceIfOppHasMana)
&& (!tradeIfLowerLifePressure || (ai.getLifeLostLastTurn() + ai.getLifeLostThisTurn() <
defendingOpponent.getLifeLostThisTurn() + defendingOpponent.getLifeLostThisTurn()))) {
this.aiAggression = 4; // random (chance-based) attack expecting to trade or damage player.
} else if (ratioDiff >= 0 && this.attackers.size() > 1) { } else if (ratioDiff >= 0 && this.attackers.size() > 1) {
this.aiAggression = 3; // attack expecting to make good trades or damage player. this.aiAggression = 3; // attack expecting to make good trades or damage player.
} else if (ratioDiff + outNumber >= -1 || aiLifeToPlayerDamageRatio > 1 } else if (ratioDiff + outNumber >= -1 || aiLifeToPlayerDamageRatio > 1
@@ -927,24 +1048,6 @@ public class AiAttackController {
} }
} // getAttackers() } // getAttackers()
/**
* <p>
* countExaltedBonus.
* </p>
*
* @param player
* a {@link forge.game.player.Player} object.
* @return a int.
*/
public final static int countExaltedBonus(final Player player) {
int bonus = 0;
for (Card c : player.getCardsIn(ZoneType.Battlefield)) {
bonus += c.getAmountOfKeyword("Exalted");
}
return bonus;
}
/** /**
* <p> * <p>
* getAttack. * getAttack.
@@ -957,7 +1060,7 @@ public class AiAttackController {
public final static int getAttack(final Card c) { public final static int getAttack(final Card c) {
int n = c.getNetCombatDamage(); int n = c.getNetCombatDamage();
if (c.hasKeyword("Double Strike")) { if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
n *= 2; n *= 2;
} }
@@ -985,7 +1088,29 @@ public class AiAttackController {
boolean isWorthLessThanAllKillers = true; boolean isWorthLessThanAllKillers = true;
boolean canBeBlocked = false; boolean canBeBlocked = false;
int numberOfPossibleBlockers = 0; int numberOfPossibleBlockers = 0;
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
if ((attacker.hasSVar("NonCombatPriority"))
&& (!attacker.hasKeyword(Keyword.VIGILANCE))) {
// For each level of priority, enemy has to have life as much as the creature's power
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
// the lower the priroity, the less willing the AI is to use the creature for attacking.
// TODO Somehow subtract expected damage of other attacking creatures from enemy life total (how? other attackers not yet declared? Can the AI guesstimate which of their creatures will not get blocked?)
if (attacker.getCurrentPower() * Integer.parseInt(attacker.getSVar("NonCombatPriority")) < ai.getOpponentsSmallestLifeTotal()) {
// Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine!
boolean wantability = false;
for (SpellAbility sa : attacker.getSpellAbilities()) {
// Do not attack if we can afford using the ability.
if (sa.isAbility()) {
if (ComputerUtilCost.canPayCost(sa, ai)) {
return false;
}
// TODO Eventually The Ai will need to learn to predict if they have any use for the ability before next untap or not.
// TODO abilities that tap enemy creatures should probably only be saved if the enemy has nonzero creatures? Haste can be a threat though...
}
}
}
}
if (!this.isEffectiveAttacker(ai, attacker, combat)) { if (!this.isEffectiveAttacker(ai, attacker, combat)) {
return false; return false;
@@ -994,9 +1119,14 @@ public class AiAttackController {
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...) // is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|| "Blocked".equals(attacker.getSVar("HasAttackEffect")); || "Blocked".equals(attacker.getSVar("HasAttackEffect"));
// total power of the defending creatures, used in predicting whether a gang block can kill the attacker
int defPower = CardLists.getTotalPower(defenders, true);
if (!hasCombatEffect) { if (!hasCombatEffect) {
for (String keyword : attacker.getKeywords()) { for (KeywordInterface inst : attacker.getKeywords()) {
if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) { String keyword = inst.getOriginal();
if (keyword.equals("Wither") || keyword.equals("Infect")
|| keyword.equals("Lifelink") || keyword.startsWith("Afflict")) {
hasCombatEffect = true; hasCombatEffect = true;
break; break;
} }
@@ -1013,7 +1143,7 @@ public class AiAttackController {
&& CombatUtil.canBlock(attacker, defender)) { && CombatUtil.canBlock(attacker, defender)) {
numberOfPossibleBlockers += 1; numberOfPossibleBlockers += 1;
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false) if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
&& !(attacker.hasKeyword("Undying") && attacker.getCounters(CounterType.P1P1) == 0)) { && !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
// see if the defending creature is of higher or lower // see if the defending creature is of higher or lower
// value. We don't want to attack only to lose value // value. We don't want to attack only to lose value
@@ -1029,12 +1159,27 @@ public class AiAttackController {
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) { if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false; canKillAllDangerous = false;
} else { } else {
for (String keyword : defender.getKeywords()) { if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT)
if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) { || defender.hasKeyword(Keyword.LIFELINK)) {
canKillAllDangerous = false;
// there is a creature that can survive an attack from this creature
// and combat will have negative effects
}
// Check if maybe we are too reckless in adding this attacker
if (canKillAllDangerous) {
boolean avoidAttackingIntoBlock = ai.getController().isAI()
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK);
boolean attackerWillDie = defPower >= attacker.getNetToughness();
boolean uselessAttack = !hasCombatEffect && !hasAttackEffect;
boolean noContributionToAttack = this.attackers.size() <= defenders.size() || attacker.getNetPower() <= 0;
// We are attacking too recklessly if we can't kill a single blocker and:
// - our creature will die for sure (chump attack)
// - our attack will not do anything special (no attack/combat effect to proc)
// - we can't deal damage to our opponent with sheer number of attackers and/or our attacker's power is 0 or less
if (attackerWillDie || (avoidAttackingIntoBlock && (uselessAttack || noContributionToAttack))) {
canKillAllDangerous = false; canKillAllDangerous = false;
break;
// there is a creature that can survive an attack from this creature
// and combat will have negative effects
} }
} }
} }
@@ -1042,7 +1187,7 @@ public class AiAttackController {
} }
} }
if (!attacker.hasKeyword("vigilance") && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) { if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
canKillAllDangerous = false; canKillAllDangerous = false;
canBeKilled = true; canBeKilled = true;
canBeKilledByOne = true; canBeKilledByOne = true;
@@ -1128,7 +1273,7 @@ public class AiAttackController {
// creature would leave the battlefield // creature would leave the battlefield
// no pain in exerting it // no pain in exerting it
shouldExert = true; shouldExert = true;
} else if (c.hasKeyword("Vigilance")) { } else if (c.hasKeyword(Keyword.VIGILANCE)) {
// Free exert - why not? // Free exert - why not?
shouldExert = true; shouldExert = true;
} }
@@ -1147,18 +1292,51 @@ public class AiAttackController {
} }
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.setActivatingPlayer(c.getController()); sa.setActivatingPlayer(c.getController());
if (CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).isEmpty()) { List<Card> validTargets = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
if (validTargets.isEmpty()) {
missTarget = true;
break;
} else if (sa.isCurse() && CardLists.filter(validTargets,
CardPredicates.isControlledByAnyOf(c.getController().getOpponents())).isEmpty()) {
// e.g. Ahn-Crop Crasher - the effect is only good when aimed at opponent's creatures
missTarget = true; missTarget = true;
break; break;
} }
} }
} }
if (missTarget) { if (missTarget) {
continue; continue;
} }
if (random.nextBoolean()) { // A specific AI condition for Exert: if specified on the card, the AI will always
// exert creatures that meet this condition
if (c.hasSVar("AIExertCondition")) {
if (!c.getSVar("AIExertCondition").isEmpty()) {
final String needsToExert = c.getSVar("AIExertCondition");
int x = 0;
int y = 0;
String sVar = needsToExert.split(" ")[0];
String comparator = needsToExert.split(" ")[1];
String compareTo = comparator.substring(2);
try {
x = Integer.parseInt(sVar);
} catch (final NumberFormatException e) {
x = CardFactoryUtil.xCount(c, c.getSVar(sVar));
}
try {
y = Integer.parseInt(compareTo);
} catch (final NumberFormatException e) {
y = CardFactoryUtil.xCount(c, c.getSVar(compareTo));
}
if (Expressions.compare(x, comparator, y)) {
shouldExert = true;
}
}
}
if (!shouldExert && MyRandom.getRandom().nextBoolean()) {
// TODO Improve when the AI wants to use Exert powers // TODO Improve when the AI wants to use Exert powers
shouldExert = true; shouldExert = true;
} }

View File

@@ -19,26 +19,21 @@ package forge.ai;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import forge.card.CardStateName;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.card.Card; import forge.game.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.CounterType;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/** /**
@@ -89,8 +84,10 @@ public class AiBlockController {
private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) { private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
final List<Card> blockers = new ArrayList<>(); final List<Card> blockers = new ArrayList<>();
// We don't check attacker static abilities at this point since the attackers have already attacked and, thus,
// their P/T modifiers are active and are counted as a part of getNetPower/getNetToughness
for (final Card b : blockersLeft) { for (final Card b : blockersLeft) {
if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false)) { if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false, true)) {
blockers.add(b); blockers.add(b);
} }
} }
@@ -102,8 +99,10 @@ public class AiBlockController {
private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) { private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
final List<Card> blockers = new ArrayList<>(); final List<Card> blockers = new ArrayList<>();
// We don't check attacker static abilities at this point since the attackers have already attacked and, thus,
// their P/T modifiers are active and are counted as a part of getNetPower/getNetToughness
for (final Card b : blockersLeft) { for (final Card b : blockersLeft) {
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false)) { if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false, true)) {
blockers.add(b); blockers.add(b);
} }
} }
@@ -119,11 +118,11 @@ public class AiBlockController {
// If I don't have any planeswalkers then sorting doesn't really matter // If I don't have any planeswalkers then sorting doesn't really matter
if (defenders.size() == 1) { if (defenders.size() == 1) {
final CardCollection attackers = combat.getAttackersOf(defenders.get(0)); final CardCollection attackers = combat.getAttackersOf(defenders.get(0));
// Begin with the attackers that pose the biggest threat // Begin with the attackers that pose the biggest threat
ComputerUtilCard.sortByEvaluateCreature(attackers); ComputerUtilCard.sortByEvaluateCreature(attackers);
CardLists.sortByPowerDesc(attackers); CardLists.sortByPowerDesc(attackers);
//move cards like Phage the Untouchable to the front //move cards like Phage the Untouchable to the front
Collections.sort(attackers, new Comparator<Card>() { Collections.sort(attackers, new Comparator<Card>() {
@Override @Override
public int compare(final Card o1, final Card o2) { public int compare(final Card o1, final Card o2) {
@@ -144,16 +143,16 @@ public class AiBlockController {
// defend planeswalkers with more loyalty before planeswalkers with less loyalty // defend planeswalkers with more loyalty before planeswalkers with less loyalty
// if planeswalker will be too difficult to defend don't even bother // if planeswalker will be too difficult to defend don't even bother
for (GameEntity defender : defenders) { for (GameEntity defender : defenders) {
if (defender instanceof Card) { if (defender instanceof Card) {
final CardCollection attackers = combat.getAttackersOf(defender); final CardCollection attackers = combat.getAttackersOf(defender);
// Begin with the attackers that pose the biggest threat // Begin with the attackers that pose the biggest threat
CardLists.sortByPowerDesc(attackers); CardLists.sortByPowerDesc(attackers);
for (final Card c : attackers) { for (final Card c : attackers) {
sortedAttackers.add(c); sortedAttackers.add(c);
} }
} else { } else if (defender instanceof Player && defender.equals(ai)) {
firstAttacker = combat.getAttackersOf(defender); firstAttacker = combat.getAttackersOf(defender);
} }
} }
if (bLifeInDanger) { if (bLifeInDanger) {
@@ -182,7 +181,7 @@ public class AiBlockController {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.") || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|| attacker.hasKeyword("Menace")) { || attacker.hasKeyword(Keyword.MENACE)) {
continue; continue;
} }
@@ -206,11 +205,11 @@ public class AiBlockController {
&& !ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) { && !ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers); blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
// check whether it's better to block a creature without trample to absorb more damage // check whether it's better to block a creature without trample to absorb more damage
if (attacker.hasKeyword("Trample")) { if (attacker.hasKeyword(Keyword.TRAMPLE)) {
boolean doNotBlock = false; boolean doNotBlock = false;
for (Card other : attackersLeft) { for (Card other : attackersLeft) {
if (other.equals(attacker) || !CombatUtil.canBlock(other, blocker) if (other.equals(attacker) || !CombatUtil.canBlock(other, blocker)
|| other.hasKeyword("Trample") || other.hasKeyword(Keyword.TRAMPLE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(other, ai) || ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|| ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false) || ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false)
|| other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { || other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
@@ -233,11 +232,10 @@ public class AiBlockController {
// 3.Blockers that can destroy the attacker and have an upside when dying // 3.Blockers that can destroy the attacker and have an upside when dying
killingBlockers = getKillingBlockers(combat, attacker, blockers); killingBlockers = getKillingBlockers(combat, attacker, blockers);
for (Card b : killingBlockers) { for (Card b : killingBlockers) {
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0) if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterType.P1P1) == 0) || b.hasSVar("SacMe")
|| b.hasSVar("SacMe") || (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|| (b.hasStartOfKeyword("Vanishing") && b.getCounters(CounterType.TIME) == 1) || (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|| (b.hasStartOfKeyword("Fading") && b.getCounters(CounterType.FADE) == 0) || b.hasSVar("EndOfTurnLeavePlay")) {
|| b.hasSVar("EndOfTurnLeavePlay")) {
blocker = b; blocker = b;
break; break;
} }
@@ -247,7 +245,7 @@ public class AiBlockController {
if (b.hasSVar("SacMe") && Integer.parseInt(b.getSVar("SacMe")) > 3) { if (b.hasSVar("SacMe") && Integer.parseInt(b.getSVar("SacMe")) > 3) {
blocker = b; blocker = b;
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) { if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
blockedButUnkilled.add(attacker); blockedButUnkilled.add(attacker);
} }
break; break;
} }
@@ -295,8 +293,7 @@ public class AiBlockController {
// 6. Blockers that don't survive until the next turn anyway // 6. Blockers that don't survive until the next turn anyway
for (final Card attacker : attackersLeft) { for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("Menace")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue; continue;
} }
@@ -306,12 +303,12 @@ public class AiBlockController {
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true); final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
for (Card b : blockers) { for (Card b : blockers) {
if ((b.hasStartOfKeyword("Vanishing") && b.getCounters(CounterType.TIME) == 1) if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|| (b.hasStartOfKeyword("Fading") && b.getCounters(CounterType.FADE) == 0) || (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|| b.hasSVar("EndOfTurnLeavePlay")) { || b.hasSVar("EndOfTurnLeavePlay")) {
blocker = b; blocker = b;
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) { if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
blockedButUnkilled.add(attacker); blockedButUnkilled.add(attacker);
} }
break; break;
} }
@@ -349,9 +346,9 @@ public class AiBlockController {
final List<Card> firstStrikeBlockers = new ArrayList<>(); final List<Card> firstStrikeBlockers = new ArrayList<>();
final List<Card> blockGang = new ArrayList<>(); final List<Card> blockGang = new ArrayList<>();
for (Card blocker : blockers) { for (Card blocker : blockers) {
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) { if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
continue; continue;
} }
if (blocker.hasFirstStrike() || blocker.hasDoubleStrike()) { if (blocker.hasFirstStrike() || blocker.hasDoubleStrike()) {
firstStrikeBlockers.add(blocker); firstStrikeBlockers.add(blocker);
} }
@@ -417,7 +414,8 @@ public class AiBlockController {
&& !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) { && !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) {
return false; return false;
} }
return lifeInDanger || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker); final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat);
return lifeInDanger || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker) || randomTrade;
} }
}); });
if (usableBlockers.size() < 2) { if (usableBlockers.size() < 2) {
@@ -445,9 +443,9 @@ public class AiBlockController {
// The attacker will be killed // The attacker will be killed
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage() && (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
// only one blocker can be killed // only one blocker can be killed
|| currentValue + addedValue - 50 <= evalAttackerValue || currentValue + addedValue - 50 <= evalAttackerValue
// or attacker is worth more // or attacker is worth more
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat))) || (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
// or life is in danger // or life is in danger
&& CombatUtil.canBlock(attacker, blocker, combat)) { && CombatUtil.canBlock(attacker, blocker, combat)) {
// this is needed for attackers that can't be blocked by // this is needed for attackers that can't be blocked by
@@ -497,7 +495,7 @@ public class AiBlockController {
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3) && !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
// The attacker will be killed // The attacker will be killed
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage && ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
&& absorbedDamage3 + absorbedDamage2 > netCombatDamage) && absorbedDamage3 + absorbedDamage2 > netCombatDamage)
// only one blocker can be killed // only one blocker can be killed
|| currentValue + addedValue2 + addedValue3 - 50 <= evalAttackerValue || currentValue + addedValue2 + addedValue3 - 50 <= evalAttackerValue
// or attacker is worth more // or attacker is worth more
@@ -526,7 +524,56 @@ public class AiBlockController {
attackersLeft = (new ArrayList<>(currentAttackers)); attackersLeft = (new ArrayList<>(currentAttackers));
} }
private void makeGangNonLethalBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
List<Card> blockers;
// Try to block a Menace attacker with two blockers, neither of which will die
for (final Card attacker : attackersLeft) {
if (!attacker.hasKeyword(Keyword.MENACE) && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
continue;
}
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
List<Card> usableBlockers;
final List<Card> blockGang = new ArrayList<>();
int absorbedDamage; // The amount of damage needed to kill the first blocker
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getNetToughness() > attacker.getNetCombatDamage();
}
});
if (usableBlockers.size() < 2) {
return;
}
final Card leader = ComputerUtilCard.getWorstCreatureAI(usableBlockers);
blockGang.add(leader);
usableBlockers.remove(leader);
absorbedDamage = ComputerUtilCombat.getEnoughDamageToKill(leader, attacker.getNetCombatDamage(), attacker, true);
// consider a double block
for (final Card blocker : usableBlockers) {
final int absorbedDamage2 = ComputerUtilCombat.getEnoughDamageToKill(blocker, attacker.getNetCombatDamage(), attacker, true);
// only do it if neither blocking creature will die
if (absorbedDamage > attacker.getNetCombatDamage() && absorbedDamage2 > attacker.getNetCombatDamage()) {
currentAttackers.remove(attacker);
combat.addBlocker(attacker, blocker);
if (CombatUtil.canBlock(attacker, leader, combat)) {
combat.addBlocker(attacker, leader);
}
break;
}
}
}
attackersLeft = (new ArrayList<>(currentAttackers));
}
// Bad Trade Blocks (should only be made if life is in danger) // Bad Trade Blocks (should only be made if life is in danger)
// Random Trade Blocks (performed randomly if enabled in profile and only when in favorable conditions)
/** /**
* <p> * <p>
* makeTradeBlocks. * makeTradeBlocks.
@@ -542,20 +589,33 @@ public class AiBlockController {
for (final Card attacker : attackersLeft) { for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("Menace") || attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue; continue;
} }
if (ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
continue;
}
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers); killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers);
if (!killingBlockers.isEmpty() && ComputerUtilCombat.lifeInDanger(ai, combat)) {
if (ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) { if (!killingBlockers.isEmpty()) {
continue;
}
final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
combat.addBlocker(attacker, blocker); boolean doTrade = false;
currentAttackers.remove(attacker);
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
// Always trade when life in danger
doTrade = true;
} else {
// Randomly trade creatures with lower power and [hopefully] worse abilities, if enabled in profile
doTrade = wouldLikeToRandomlyTrade(attacker, blocker, combat);
}
if (doTrade) {
combat.addBlocker(attacker, blocker);
currentAttackers.remove(attacker);
}
} }
} }
attackersLeft = (new ArrayList<>(currentAttackers)); attackersLeft = (new ArrayList<>(currentAttackers));
@@ -584,7 +644,7 @@ public class AiBlockController {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.") || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|| attacker.hasKeyword("Menace") || attacker.hasKeyword(Keyword.MENACE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) { || ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
attackers.remove(0); attackers.remove(0);
makeChumpBlocks(combat, attackers); makeChumpBlocks(combat, attackers);
@@ -596,7 +656,7 @@ public class AiBlockController {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers); final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
// check if it's better to block a creature with lower power and without trample // check if it's better to block a creature with lower power and without trample
if (attacker.hasKeyword("Trample")) { if (attacker.hasKeyword(Keyword.TRAMPLE)) {
final int damageAbsorbed = blocker.getLethalDamage(); final int damageAbsorbed = blocker.getLethalDamage();
if (attacker.getNetCombatDamage() > damageAbsorbed) { if (attacker.getNetCombatDamage() > damageAbsorbed) {
for (Card other : attackers) { for (Card other : attackers) {
@@ -604,7 +664,7 @@ public class AiBlockController {
continue; continue;
} }
if (other.getNetCombatDamage() >= damageAbsorbed if (other.getNetCombatDamage() >= damageAbsorbed
&& !other.hasKeyword("Trample") && !other.hasKeyword(Keyword.TRAMPLE)
&& !other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") && !other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
&& !ComputerUtilCombat.attackerHasThreateningAfflict(other, ai) && !ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
&& CombatUtil.canBlock(other, blocker, combat)) { && CombatUtil.canBlock(other, blocker, combat)) {
@@ -635,7 +695,7 @@ public class AiBlockController {
for (final Card attacker : currentAttackers) { for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
&& !attacker.hasKeyword("Menace") && !attacker.hasKeyword(Keyword.MENACE)
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { && !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue; continue;
} }
@@ -668,7 +728,7 @@ public class AiBlockController {
List<Card> chumpBlockers; List<Card> chumpBlockers;
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, "Trample"); List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock)); tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced // TODO - should check here for a "rampage-like" trigger that replaced
@@ -677,7 +737,7 @@ public class AiBlockController {
for (final Card attacker : tramplingAttackers) { for (final Card attacker : tramplingAttackers) {
if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) && !combat.isBlocked(attacker)) if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) && !combat.isBlocked(attacker))
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) { || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue; continue;
@@ -714,29 +774,29 @@ public class AiBlockController {
// Try to use safe blockers first // Try to use safe blockers first
if (blockers.size() > 0) { if (blockers.size() > 0) {
safeBlockers = getSafeBlockers(combat, attacker, blockers); safeBlockers = getSafeBlockers(combat, attacker, blockers);
for (final Card blocker : safeBlockers) { for (final Card blocker : safeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// Add an additional blocker if the current blockers are not // Add an additional blocker if the current blockers are not
// enough and the new one would deal additional damage // enough and the new one would deal additional damage
if (damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)) if (damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))
&& ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0 && ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
&& CombatUtil.canBlock(attacker, blocker, combat)) { && CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker); combat.addBlocker(attacker, blocker);
} }
blockers.remove(blocker); // Don't check them again next blockers.remove(blocker); // Don't check them again next
} }
} }
// don't try to kill what can't be killed // don't try to kill what can't be killed
if (attacker.hasKeyword("indestructible") || ComputerUtil.canRegenerate(ai, attacker)) { if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(ai, attacker)) {
continue; continue;
} }
// Try to add blockers that could be destroyed, but are worth less than the attacker // Try to add blockers that could be destroyed, but are worth less than the attacker
// Don't use blockers without First Strike or Double Strike if attacker has it // Don't use blockers without First Strike or Double Strike if attacker has it
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false, combat)) { if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false, combat)) {
safeBlockers = CardLists.getKeyword(blockers, "First Strike"); safeBlockers = CardLists.getKeyword(blockers, Keyword.FIRST_STRIKE);
safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike")); safeBlockers.addAll(CardLists.getKeyword(blockers, Keyword.DOUBLE_STRIKE));
} else { } else {
safeBlockers = new ArrayList<>(blockers); safeBlockers = new ArrayList<>(blockers);
} }
@@ -760,6 +820,105 @@ public class AiBlockController {
} }
} }
private void makeChumpBlocksToSavePW(Combat combat) {
if (ComputerUtilCombat.lifeInDanger(ai, combat) || ai.getLife() <= ai.getStartingLife() / 5) {
// most likely not worth trying to protect planeswalkers when at threateningly low life or in
// dangerous combat which threatens lethal or severe damage to face
return;
}
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
final int evalThresholdToken = aic.getIntProperty(AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER);
final int evalThresholdNonToken = aic.getIntProperty(AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER);
final boolean onlyIfLethal = aic.getBooleanProperty(AiProps.CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL);
if (evalThresholdToken > 0 || evalThresholdNonToken > 0) {
// detect how much damage is threatened to each of the planeswalkers, see which ones would be
// worth protecting according to the AI profile properties
CardCollection threatenedPWs = new CardCollection();
for (final Card attacker : attackers) {
GameEntity def = combat.getDefenderByAttacker(attacker);
if (def instanceof Card) {
if (!onlyIfLethal) {
threatenedPWs.add((Card) def);
} else {
int damageToPW = 0;
for (final Card pwatkr : combat.getAttackersOf(def)) {
if (!combat.isBlocked(pwatkr)) {
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
}
}
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= ((Card) def).getCounters(CounterType.LOYALTY)) {
threatenedPWs.add((Card) def);
}
}
}
}
CardCollection pwsWithChumpBlocks = new CardCollection();
CardCollection chosenChumpBlockers = new CardCollection();
CardCollection chumpPWDefenders = CardLists.filter(new CardCollection(this.blockersLeft), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.evaluateCreature(card) <= (card.isToken() ? evalThresholdToken
: evalThresholdNonToken);
}
});
CardLists.sortByPowerAsc(chumpPWDefenders);
if (!chumpPWDefenders.isEmpty()) {
for (final Card attacker : attackers) {
GameEntity def = combat.getDefenderByAttacker(attacker);
if (def instanceof Card && threatenedPWs.contains((Card) def)) {
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
// don't bother trying to chump a trampling creature
continue;
}
if (!combat.getBlockers(attacker).isEmpty()) {
// already blocked by something, no need to chump
continue;
}
Card blockerDecided = null;
for (final Card blocker : chumpPWDefenders) {
if (CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
pwsWithChumpBlocks.add((Card) combat.getDefenderByAttacker(attacker));
chosenChumpBlockers.add(blocker);
blockerDecided = blocker;
blockersLeft.remove(blocker);
break;
}
}
chumpPWDefenders.remove(blockerDecided);
}
}
// check to see if we managed to cover all the blockers of the planeswalker; if not, bail
for (final Card pw : pwsWithChumpBlocks) {
CardCollection pwAttackers = combat.getAttackersOf(pw);
CardCollection pwDefenders = new CardCollection();
boolean isFullyBlocked = true;
if (!pwAttackers.isEmpty()) {
int damageToPW = 0;
for (Card pwAtk : pwAttackers) {
if (!combat.getBlockers(pwAtk).isEmpty()) {
pwDefenders.addAll(combat.getBlockers(pwAtk));
} else {
isFullyBlocked = false;
damageToPW += ComputerUtilCombat.predictDamageTo((Card) pw, pwAtk.getNetCombatDamage(), pwAtk, true);
}
}
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
for (Card chump : pwDefenders) {
if (chosenChumpBlockers.contains(chump)) {
combat.removeFromCombat(chump);
}
}
}
}
}
}
}
}
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) { private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
final List<Card> oldBlockers = combat.getAllBlockers(); final List<Card> oldBlockers = combat.getAllBlockers();
@@ -824,8 +983,8 @@ public class AiBlockController {
List<Card> chumpBlockers; List<Card> chumpBlockers;
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getProperty(AiProps.PLAY_AGGRO).equals("true")) { if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
diff = 0; diff = 0;
} }
// remove all attackers that can't be blocked anyway // remove all attackers that can't be blocked anyway
@@ -855,78 +1014,85 @@ public class AiBlockController {
// When the AI holds some Fog effect, don't bother about lifeInDanger // When the AI holds some Fog effect, don't bother about lifeInDanger
if (!ComputerUtil.hasAFogEffect(ai)) { if (!ComputerUtil.hasAFogEffect(ai)) {
lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat); lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
if (lifeInDanger) { makeTradeBlocks(combat); // choose necessary trade blocks
makeTradeBlocks(combat); // choose necessary trade blocks
} // if life is still in danger
// if life is still in danger if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) { makeChumpBlocks(combat); // choose necessary chump blocks
makeChumpBlocks(combat); // choose necessary chump blocks } else {
} else { lifeInDanger = false;
lifeInDanger = false;
} }
// if life is still in danger // if life is still in danger
// Reinforce blockers blocking attackers with trample if life is still // Reinforce blockers blocking attackers with trample if life is
// in danger // still
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) { // in danger
reinforceBlockersAgainstTrample(combat); if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
} else { reinforceBlockersAgainstTrample(combat);
lifeInDanger = false; } else {
lifeInDanger = false;
}
// Support blockers not destroying the attacker with more blockers
// to
// try to kill the attacker
if (!lifeInDanger) {
reinforceBlockersToKill(combat);
} }
// Support blockers not destroying the attacker with more blockers to
// try to kill the attacker
if (!lifeInDanger) {
reinforceBlockersToKill(combat);
}
// == 2. If the AI life would still be in danger make a safer approach == // == 2. If the AI life would still be in danger make a safer
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) { // approach ==
clearBlockers(combat, possibleBlockers); // reset every block assignment if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade blocks clearBlockers(combat, possibleBlockers); // reset every block
// if life is in danger // assignment
makeGoodBlocks(combat); makeTradeBlocks(combat); // choose necessary trade blocks
// choose necessary chump blocks if life is still in danger // if life is in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { makeGoodBlocks(combat);
makeChumpBlocks(combat); // choose necessary chump blocks if life is still in danger
} else { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
lifeInDanger = false; makeChumpBlocks(combat);
} } else {
// Reinforce blockers blocking attackers with trample if life is lifeInDanger = false;
// still in danger }
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) { // Reinforce blockers blocking attackers with trample if life is
reinforceBlockersAgainstTrample(combat); // still in danger
} else { if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
lifeInDanger = false; reinforceBlockersAgainstTrample(combat);
} } else {
makeGangBlocks(combat); lifeInDanger = false;
reinforceBlockersToKill(combat); }
} makeGangBlocks(combat);
reinforceBlockersToKill(combat);
}
// == 3. If the AI life would be in serious danger make an even safer approach == // == 3. If the AI life would be in serious danger make an even
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) { // safer approach ==
clearBlockers(combat, possibleBlockers); // reset every block assignment if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
makeChumpBlocks(combat); // choose chump blocks clearBlockers(combat, possibleBlockers); // reset every block
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { // assignment
makeTradeBlocks(combat); // choose necessary trade makeChumpBlocks(combat); // choose chump blocks
} if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade
}
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeGoodBlocks(combat); makeGoodBlocks(combat);
} }
// Reinforce blockers blocking attackers with trample if life is // Reinforce blockers blocking attackers with trample if life is
// still in danger // still in danger
else { else {
reinforceBlockersAgainstTrample(combat); reinforceBlockersAgainstTrample(combat);
} }
makeGangBlocks(combat); makeGangBlocks(combat);
// Support blockers not destroying the attacker with more blockers // Support blockers not destroying the attacker with more
// to try to kill the attacker // blockers
reinforceBlockersToKill(combat); // to try to kill the attacker
} reinforceBlockersToKill(combat);
}
} }
// assign blockers that have to block // assign blockers that have to block
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able."); chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
// if an attacker with lure attacks - all that can block // if an attacker with lure attacks - all that can block
for (final Card blocker : blockersLeft) { for (final Card blocker : blockersLeft) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) { if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
@@ -940,7 +1106,8 @@ public class AiBlockController {
for (final Card blocker : blockers) { for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker) if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat) && (CombatUtil.mustBlockAnAttacker(blocker, combat)
|| blocker.hasKeyword("CARDNAME blocks each turn if able."))) { || blocker.hasKeyword("CARDNAME blocks each turn if able.")
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
combat.addBlocker(attacker, blocker); combat.addBlocker(attacker, blocker);
if (blocker.getMustBlockCards() != null) { if (blocker.getMustBlockCards() != null) {
int mustBlockAmt = blocker.getMustBlockCards().size(); int mustBlockAmt = blocker.getMustBlockCards().size();
@@ -956,6 +1123,18 @@ public class AiBlockController {
} }
} }
} }
// check to see if it's possible to defend a Planeswalker under attack with a chump block,
// unless life is low enough to be more worried about saving preserving the life total
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocksToSavePW(combat);
}
// if there are still blockers left, see if it's possible to block Menace creatures with
// non-lethal blockers that won't kill the attacker but won't die to it as well
makeGangNonLethalBlocks(combat);
//Check for validity of blocks in case something slipped through //Check for validity of blocks in case something slipped through
for (Card attacker : attackers) { for (Card attacker : attackers) {
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size(), combat)) { if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size(), combat)) {
@@ -994,10 +1173,11 @@ public class AiBlockController {
* Orders a blocker that put onto the battlefield blocking. Depends heavily * Orders a blocker that put onto the battlefield blocking. Depends heavily
* on the implementation of orderBlockers(). * on the implementation of orderBlockers().
*/ */
public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) { public static CardCollection orderBlocker(final Card attacker, final Card blocker,
// add blocker to existing ordering final CardCollection oldBlockers) {
// sort by evaluate, then insert it appropriately // add blocker to existing ordering
// relies on current implementation of orderBlockers() // sort by evaluate, then insert it appropriately
// relies on current implementation of orderBlockers()
final CardCollection allBlockers = new CardCollection(oldBlockers); final CardCollection allBlockers = new CardCollection(oldBlockers);
allBlockers.add(blocker); allBlockers.add(blocker);
ComputerUtilCard.sortByEvaluateCreature(allBlockers); ComputerUtilCard.sortByEvaluateCreature(allBlockers);
@@ -1009,24 +1189,28 @@ public class AiBlockController {
boolean newBlockerIsAdded = false; boolean newBlockerIsAdded = false;
// The new blocker comes right after this one // The new blocker comes right after this one
final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1)); final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1));
if (newBlockerRightAfter == null && damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) { if (newBlockerRightAfter == null
result.add(blocker); && damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
newBlockerIsAdded = true; result.add(blocker);
newBlockerIsAdded = true;
} }
// Don't bother to keep damage up-to-date after the new blocker is added, as we can't modify the order of the other cards anyway // Don't bother to keep damage up-to-date after the new blocker is
// added, as we can't modify the order of the other cards anyway
for (final Card c : oldBlockers) { for (final Card c : oldBlockers) {
final int lethal = ComputerUtilCombat.getEnoughDamageToKill(c, damage, attacker, true); final int lethal = ComputerUtilCombat.getEnoughDamageToKill(c, damage, attacker, true);
damage -= lethal; damage -= lethal;
result.add(c); result.add(c);
if (!newBlockerIsAdded && c == newBlockerRightAfter && damage <= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) { if (!newBlockerIsAdded && c == newBlockerRightAfter
// If blocker is right after this card in priority and we have sufficient damage to kill it, add it here && damage <= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
result.add(blocker); // If blocker is right after this card in priority and we have
newBlockerIsAdded = true; // sufficient damage to kill it, add it here
} result.add(blocker);
newBlockerIsAdded = true;
}
} }
// We don't have sufficient damage, just add it at the end! // We don't have sufficient damage, just add it at the end!
if (!newBlockerIsAdded) { if (!newBlockerIsAdded) {
result.add(blocker); result.add(blocker);
} }
return result; return result;
@@ -1056,4 +1240,91 @@ public class AiBlockController {
return first; return first;
} }
private boolean wouldLikeToRandomlyTrade(Card attacker, Card blocker, Combat combat) {
// Determines if the AI would like to randomly trade its blocker for the attacker in given combat
boolean enableRandomTrades = false;
boolean randomTradeIfBehindOnBoard = false;
boolean randomTradeIfCreatInHand = false;
int chanceModForEmbalm = 0;
int chanceToTradeToSaveWalker = 0;
int chanceToTradeDownToSaveWalker = 0;
int minRandomTradeChance = 0;
int maxRandomTradeChance = 0;
int maxCreatDiff = 0;
int maxCreatDiffWithRepl = 0;
int aiCreatureCount = 0;
int oppCreatureCount = 0;
if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM);
maxCreatDiff = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE);
maxCreatDiffWithRepl = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL);
chanceToTradeToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER);
chanceToTradeDownToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER);
}
if (!enableRandomTrades) {
return false;
}
aiCreatureCount = ComputerUtil.countUsefulCreatures(ai);
if (!attackersLeft.isEmpty()) {
oppCreatureCount = ComputerUtil.countUsefulCreatures(attackersLeft.get(0).getController());
}
if (attacker.getOwner().equals(ai) && "6".equals(attacker.getSVar("SacMe"))) {
// Temporarily controlled object - don't trade with it
// TODO: find a more reliable way to figure out that control will be reestablished next turn
return false;
}
int numSteps = ai.getStartingLife() - 5; // e.g. 15 steps between 5 life and 20 life
float chanceStep = (maxRandomTradeChance - minRandomTradeChance) / numSteps;
int chance = (int)Math.max(minRandomTradeChance, (maxRandomTradeChance - (Math.max(5, ai.getLife() - 5)) * chanceStep));
if (chance > maxRandomTradeChance) {
chance = maxRandomTradeChance;
}
int evalAtk = ComputerUtilCard.evaluateCreature(attacker, true, false);
int evalBlk = ComputerUtilCard.evaluateCreature(blocker, true, false);
boolean atkEmbalm = (attacker.hasStartOfKeyword("Embalm") || attacker.hasStartOfKeyword("Eternalize")) && !attacker.isToken();
boolean blkEmbalm = (blocker.hasStartOfKeyword("Embalm") || blocker.hasStartOfKeyword("Eternalize")) && !blocker.isToken();
if (atkEmbalm && !blkEmbalm) {
// The opponent will eventually get his creature back, while the AI won't
chance = Math.max(0, chance - chanceModForEmbalm);
}
if (blocker.isFaceDown() && blocker.getState(CardStateName.Original).getType().isCreature()) {
// if the blocker is a face-down creature (e.g. cast via Morph, Manifest), evaluate it
// in relation to the original state, not to the Morph state
evalBlk = ComputerUtilCard.evaluateCreature(Card.fromPaperCard(blocker.getPaperCard(), ai), false, true);
}
int chanceToSavePW = chanceToTradeDownToSaveWalker > 0 && evalAtk + 1 < evalBlk ? chanceToTradeDownToSaveWalker : chanceToTradeToSaveWalker;
boolean powerParityOrHigher = blocker.getNetPower() <= attacker.getNetPower();
boolean creatureParityOrAllowedDiff = aiCreatureCount
+ (randomTradeIfBehindOnBoard ? maxCreatDiff : 0) >= oppCreatureCount;
boolean wantToTradeWithCreatInHand = randomTradeIfCreatInHand
&& !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.CREATURES).isEmpty()
&& aiCreatureCount + maxCreatDiffWithRepl >= oppCreatureCount;
boolean wantToSavePlaneswalker = MyRandom.percentTrue(chanceToSavePW)
&& combat.getDefenderByAttacker(attacker) instanceof Card
&& ((Card) combat.getDefenderByAttacker(attacker)).isPlaneswalker();
boolean wantToTradeDownToSavePW = chanceToTradeDownToSaveWalker > 0;
if (((evalBlk <= evalAtk + 1) || (wantToSavePlaneswalker && wantToTradeDownToSavePW)) // "1" accounts for tapped.
&& powerParityOrHigher
&& (creatureParityOrAllowedDiff || wantToTradeWithCreatInHand)
&& (MyRandom.percentTrue(chance) || wantToSavePlaneswalker)) {
return true;
}
return false;
}
} }

View File

@@ -41,17 +41,27 @@ import java.util.Set;
public class AiCardMemory { public class AiCardMemory {
private final Set<Card> memMandatoryAttackers; private final Set<Card> memMandatoryAttackers;
private final Set<Card> memTrickAttackers;
private final Set<Card> memHeldManaSources; private final Set<Card> memHeldManaSources;
private final Set<Card> memHeldManaSourcesForCombat;
private final Set<Card> memHeldManaSourcesForEnemyCombat;
private final Set<Card> memAttachedThisTurn; private final Set<Card> memAttachedThisTurn;
private final Set<Card> memAnimatedThisTurn; private final Set<Card> memAnimatedThisTurn;
private final Set<Card> memBouncedThisTurn; private final Set<Card> memBouncedThisTurn;
private final Set<Card> memActivatedThisTurn;
private final Set<Card> memChosenFogEffect;
public AiCardMemory() { public AiCardMemory() {
this.memMandatoryAttackers = new HashSet<>(); this.memMandatoryAttackers = new HashSet<>();
this.memHeldManaSources = new HashSet<>(); this.memHeldManaSources = new HashSet<>();
this.memHeldManaSourcesForCombat = new HashSet<>();
this.memHeldManaSourcesForEnemyCombat = new HashSet<>();
this.memAttachedThisTurn = new HashSet<>(); this.memAttachedThisTurn = new HashSet<>();
this.memAnimatedThisTurn = new HashSet<>(); this.memAnimatedThisTurn = new HashSet<>();
this.memBouncedThisTurn = new HashSet<>(); this.memBouncedThisTurn = new HashSet<>();
this.memActivatedThisTurn = new HashSet<>();
this.memTrickAttackers = new HashSet<>();
this.memChosenFogEffect = new HashSet<>();
} }
/** /**
@@ -61,10 +71,15 @@ public class AiCardMemory {
*/ */
public enum MemorySet { public enum MemorySet {
MANDATORY_ATTACKERS, MANDATORY_ATTACKERS,
HELD_MANA_SOURCES, TRICK_ATTACKERS,
HELD_MANA_SOURCES_FOR_MAIN2,
HELD_MANA_SOURCES_FOR_DECLBLK,
HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK,
ATTACHED_THIS_TURN, ATTACHED_THIS_TURN,
ANIMATED_THIS_TURN, ANIMATED_THIS_TURN,
BOUNCED_THIS_TURN, BOUNCED_THIS_TURN,
ACTIVATED_THIS_TURN,
CHOSEN_FOG_EFFECT,
//REVEALED_CARDS // stub, not linked to AI code yet //REVEALED_CARDS // stub, not linked to AI code yet
} }
@@ -72,14 +87,24 @@ public class AiCardMemory {
switch (set) { switch (set) {
case MANDATORY_ATTACKERS: case MANDATORY_ATTACKERS:
return memMandatoryAttackers; return memMandatoryAttackers;
case HELD_MANA_SOURCES: case TRICK_ATTACKERS:
return memTrickAttackers;
case HELD_MANA_SOURCES_FOR_MAIN2:
return memHeldManaSources; return memHeldManaSources;
case HELD_MANA_SOURCES_FOR_DECLBLK:
return memHeldManaSourcesForCombat;
case HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK:
return memHeldManaSourcesForEnemyCombat;
case ATTACHED_THIS_TURN: case ATTACHED_THIS_TURN:
return memAttachedThisTurn; return memAttachedThisTurn;
case ANIMATED_THIS_TURN: case ANIMATED_THIS_TURN:
return memAnimatedThisTurn; return memAnimatedThisTurn;
case BOUNCED_THIS_TURN: case BOUNCED_THIS_TURN:
return memBouncedThisTurn; return memBouncedThisTurn;
case ACTIVATED_THIS_TURN:
return memActivatedThisTurn;
case CHOSEN_FOG_EFFECT:
return memChosenFogEffect;
//case REVEALED_CARDS: //case REVEALED_CARDS:
// return memRevealedCards; // return memRevealedCards;
default: default:
@@ -249,10 +274,15 @@ public class AiCardMemory {
*/ */
public void clearAllRemembered() { public void clearAllRemembered() {
clearMemorySet(MemorySet.MANDATORY_ATTACKERS); clearMemorySet(MemorySet.MANDATORY_ATTACKERS);
clearMemorySet(MemorySet.HELD_MANA_SOURCES); clearMemorySet(MemorySet.TRICK_ATTACKERS);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
clearMemorySet(MemorySet.ATTACHED_THIS_TURN); clearMemorySet(MemorySet.ATTACHED_THIS_TURN);
clearMemorySet(MemorySet.ANIMATED_THIS_TURN); clearMemorySet(MemorySet.ANIMATED_THIS_TURN);
clearMemorySet(MemorySet.BOUNCED_THIS_TURN); clearMemorySet(MemorySet.BOUNCED_THIS_TURN);
clearMemorySet(MemorySet.ACTIVATED_THIS_TURN);
clearMemorySet(MemorySet.CHOSEN_FOG_EFFECT);
} }
// Static functions to simplify access to AI card memory of a given AI player. // Static functions to simplify access to AI card memory of a given AI player.
@@ -265,6 +295,9 @@ public class AiCardMemory {
public static boolean isRememberedCard(Player ai, Card c, MemorySet set) { public static boolean isRememberedCard(Player ai, Card c, MemorySet set) {
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(c, set); return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(c, set);
} }
public static boolean isRememberedCardByName(Player ai, String name, MemorySet set) {
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCardByName(name, set);
}
public static void clearMemorySet(Player ai, MemorySet set) { public static void clearMemorySet(Player ai, MemorySet set) {
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearMemorySet(set); ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearMemorySet(set);
} }

View File

@@ -17,14 +17,6 @@
*/ */
package forge.ai; package forge.ai;
import java.security.InvalidParameterException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.esotericsoftware.minlog.Log; import com.esotericsoftware.minlog.Log;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@@ -32,52 +24,32 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.ai.ability.ChangeZoneAi; import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.ExploreAi;
import forge.ai.simulation.SpellAbilityPicker; import forge.ai.simulation.SpellAbilityPicker;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.deck.CardPool; import forge.deck.CardPool;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.CardTraitBase; import forge.game.*;
import forge.game.CardTraitPredicates;
import forge.game.Direction;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.SpellApiBased; import forge.game.ability.SpellApiBased;
import forge.game.card.Card; import forge.game.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.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.Cost; import forge.game.cost.*;
import forge.game.cost.CostDiscard; import forge.game.keyword.Keyword;
import forge.game.cost.CostPart;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostRemoveCounter;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.replacement.ReplaceMoved; import forge.game.replacement.ReplaceMoved;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilityManaPart; import forge.game.spellability.*;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityCondition;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.SpellPermanent;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility; import forge.game.trigger.WrappedAbility;
@@ -88,6 +60,10 @@ import forge.util.Expressions;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import java.security.InvalidParameterException;
import java.util.*;
import java.util.Map.Entry;
/** /**
* <p> * <p>
* AiController class. * AiController class.
@@ -151,10 +127,12 @@ public class AiController {
private List<SpellAbility> getPossibleETBCounters() { private List<SpellAbility> getPossibleETBCounters() {
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand)); CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
CardCollectionView ccvPlayerLibrary = player.getCardsIn(ZoneType.Library);
all.addAll(player.getCardsIn(ZoneType.Exile)); all.addAll(player.getCardsIn(ZoneType.Exile));
all.addAll(player.getCardsIn(ZoneType.Graveyard)); all.addAll(player.getCardsIn(ZoneType.Graveyard));
if (!player.getCardsIn(ZoneType.Library).isEmpty()) { if (!ccvPlayerLibrary.isEmpty()) {
all.add(player.getCardsIn(ZoneType.Library).get(0)); all.add(ccvPlayerLibrary.get(0));
} }
for (final Player opp : player.getOpponents()) { for (final Player opp : player.getOpponents()) {
@@ -177,30 +155,34 @@ public class AiController {
// look for cards on the battlefield that should prevent the AI from using that spellability // look for cards on the battlefield that should prevent the AI from using that spellability
private boolean checkCurseEffects(final SpellAbility sa) { private boolean checkCurseEffects(final SpellAbility sa) {
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { CardCollectionView ccvGameBattlefield = game.getCardsIn(ZoneType.Battlefield);
for (final Card c : ccvGameBattlefield) {
if (c.hasSVar("AICurseEffect")) { if (c.hasSVar("AICurseEffect")) {
final String curse = c.getSVar("AICurseEffect"); final String curse = c.getSVar("AICurseEffect");
final Card host = sa.getHostCard();
if ("NonActive".equals(curse) && !player.equals(game.getPhaseHandler().getPlayerTurn())) { if ("NonActive".equals(curse) && !player.equals(game.getPhaseHandler().getPlayerTurn())) {
return true; return true;
} else if ("DestroyCreature".equals(curse) && sa.isSpell() && host.isCreature() } else {
&& !sa.getHostCard().hasKeyword("Indestructible")) { final Card host = sa.getHostCard();
return true; if ("DestroyCreature".equals(curse) && sa.isSpell() && host.isCreature()
} else if ("CounterEnchantment".equals(curse) && sa.isSpell() && host.isEnchantment() && !host.hasKeyword(Keyword.INDESTRUCTIBLE)) {
&& !sa.getHostCard().hasKeyword("CARDNAME can't be countered.")) { return true;
return true; } else if ("CounterEnchantment".equals(curse) && sa.isSpell() && host.isEnchantment()
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && !host.hasKeyword("CARDNAME can't be countered.") && CardFactoryUtil.isCounterable(host)) {
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) { return true;
return true; } else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && !host.hasKeyword("CARDNAME can't be countered.")) { && host.getCMC() == c.getCounters(CounterType.CHARGE)) {
for (Card card : game.getCardsIn(ZoneType.Battlefield)) { return true;
if (!card.isToken() && card.getName().equals(host.getName())) { } else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
return true; String hostName = host.getName();
for (Card card : ccvGameBattlefield) {
if (!card.isToken() && card.getName().equals(hostName)) {
return true;
}
} }
} for (Card card : game.getCardsIn(ZoneType.Graveyard)) {
for (Card card : game.getCardsIn(ZoneType.Graveyard)) { if (card.getName().equals(hostName)) {
if (card.getName().equals(host.getName())) { return true;
return true; }
} }
} }
} }
@@ -210,13 +192,14 @@ public class AiController {
} }
public boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api) { public boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api) {
boolean rightapi = false;
if (card.isCreature() if (card.isCreature()
&& game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) { && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) {
return api == null; return api == null;
} }
boolean rightapi = false;
String battlefield = ZoneType.Battlefield.toString();
Player activatingPlayer = sa.getActivatingPlayer();
// Trigger play improvements // Trigger play improvements
for (final Trigger tr : card.getTriggers()) { for (final Trigger tr : card.getTriggers()) {
// These triggers all care for ETB effects // These triggers all care for ETB effects
@@ -226,21 +209,22 @@ public class AiController {
continue; continue;
} }
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { if (!params.get("Destination").equals(battlefield)) {
continue; continue;
} }
if (params.containsKey("ValidCard")) { if (params.containsKey("ValidCard")) {
if (!params.get("ValidCard").contains("Self")) { String validCard = params.get("ValidCard");
if (!validCard.contains("Self")) {
continue; continue;
} }
if (params.get("ValidCard").contains("notkicked")) { if (validCard.contains("notkicked")) {
if (sa.isKicked()) { if (sa.isKicked()) {
continue; continue;
} }
} else if (params.get("ValidCard").contains("kicked")) { } else if (validCard.contains("kicked")) {
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker if (validCard.contains("kicked ")) { // want a specific kicker
String s = params.get("ValidCard").split("kicked ")[1]; String s = validCard.split("kicked ")[1];
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else if (!sa.isKicked()) { } else if (!sa.isKicked()) {
@@ -283,7 +267,7 @@ public class AiController {
} }
if (sa != null) { if (sa != null) {
exSA.setActivatingPlayer(sa.getActivatingPlayer()); exSA.setActivatingPlayer(activatingPlayer);
} }
else { else {
exSA.setActivatingPlayer(player); exSA.setActivatingPlayer(player);
@@ -291,13 +275,11 @@ public class AiController {
exSA.setTrigger(true); exSA.setTrigger(true);
// for trigger test, need to ignore the conditions // for trigger test, need to ignore the conditions
if (exSA.getConditions() != null) { SpellAbilityCondition cons = exSA.getConditions();
SpellAbilityCondition cons = exSA.getConditions(); if (cons != null) {
if (cons.getIsPresent() != null) { String pres = cons.getIsPresent();
String pres = cons.getIsPresent(); if (pres != null && pres.matches("Card\\.(Strictly)?Self")) {
if ("Card.Self".equals(pres) || "Card.StrictlySelf".equals(pres)) {
cons.setIsPresent(null); cons.setIsPresent(null);
}
} }
} }
@@ -321,21 +303,22 @@ public class AiController {
continue; continue;
} }
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { if (!params.get("Destination").equals(battlefield)) {
continue; continue;
} }
if (params.containsKey("ValidCard")) { if (params.containsKey("ValidCard")) {
if (!params.get("ValidCard").contains("Self")) { String validCard = params.get("ValidCard");
if (!validCard.contains("Self")) {
continue; continue;
} }
if (params.get("ValidCard").contains("notkicked")) { if (validCard.contains("notkicked")) {
if (sa.isKicked()) { if (sa.isKicked()) {
continue; continue;
} }
} else if (params.get("ValidCard").contains("kicked")) { } else if (validCard.contains("kicked")) {
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker if (validCard.contains("kicked ")) { // want a specific kicker
String s = params.get("ValidCard").split("kicked ")[1]; String s = validCard.split("kicked ")[1];
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else if (!sa.isKicked()) { // otherwise just any must be present } else if (!sa.isKicked()) { // otherwise just any must be present
@@ -351,7 +334,7 @@ public class AiController {
if (exSA != null) { if (exSA != null) {
if (sa != null) { if (sa != null) {
exSA.setActivatingPlayer(sa.getActivatingPlayer()); exSA.setActivatingPlayer(activatingPlayer);
} }
else { else {
exSA.setActivatingPlayer(player); exSA.setActivatingPlayer(player);
@@ -399,8 +382,9 @@ public class AiController {
if (landsInPlay.size() + landList.size() > max) { if (landsInPlay.size() + landList.size() > max) {
for (Card c : allCards) { for (Card c : allCards) {
for (SpellAbility sa : c.getSpellAbilities()) { for (SpellAbility sa : c.getSpellAbilities()) {
if (sa.getPayCosts() != null) { Cost payCosts = sa.getPayCosts();
for (CostPart part : sa.getPayCosts().getCostParts()) { if (payCosts != null) {
for (CostPart part : payCosts.getCostParts()) {
if (part instanceof CostDiscard) { if (part instanceof CostDiscard) {
return null; return null;
} }
@@ -414,10 +398,11 @@ public class AiController {
landList = CardLists.filter(landList, new Predicate<Card>() { landList = CardLists.filter(landList, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
CardCollectionView battlefield = player.getCardsIn(ZoneType.Battlefield);
canPlaySpellBasic(c, null); canPlaySpellBasic(c, null);
if (c.getType().isLegendary() && !c.getName().equals("Flagstones of Trokair")) { String name = c.getName();
final CardCollectionView list = player.getCardsIn(ZoneType.Battlefield); if (c.getType().isLegendary() && !name.equals("Flagstones of Trokair")) {
if (Iterables.any(list, CardPredicates.nameEquals(c.getName()))) { if (Iterables.any(battlefield, CardPredicates.nameEquals(name))) {
return false; return false;
} }
} }
@@ -426,7 +411,7 @@ public class AiController {
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities(); final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand); final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
CardCollection lands = new CardCollection(player.getCardsIn(ZoneType.Battlefield)); CardCollection lands = new CardCollection(battlefield);
lands.addAll(hand); lands.addAll(hand);
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS); lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc); int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
@@ -437,7 +422,8 @@ public class AiController {
} }
} }
} }
return true;
return player.canPlayLand(c);
} }
}); });
return landList; return landList;
@@ -589,14 +575,17 @@ public class AiController {
Collections.sort(all, saComparator); // put best spells first Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) { for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) { ApiType saApi = sa.getApi();
if (saApi == ApiType.Counter || saApi == exceptSA) {
continue; continue;
} }
sa.setActivatingPlayer(player); sa.setActivatingPlayer(player);
// TODO: this currently only works as a limited prediction of permanent spells. // TODO: this currently only works as a limited prediction of permanent spells.
// Ideally this should cast canPlaySa to determine that the AI is truly able/willing to cast a spell, // Ideally this should cast canPlaySa to determine that the AI is truly able/willing to cast a spell,
// but that is currently difficult to implement due to various side effects leading to stack overflow. // but that is currently difficult to implement due to various side effects leading to stack overflow.
if (!ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().isLand() && ComputerUtilCost.canPayCost(sa, player)) { Card host = sa.getHostCard();
if (!ComputerUtil.castPermanentInMain1(player, sa) && host != null && !host.isLand() && ComputerUtilCost.canPayCost(sa, player)) {
if (sa instanceof SpellPermanent) { if (sa instanceof SpellPermanent) {
return sa; return sa;
} }
@@ -605,11 +594,33 @@ public class AiController {
return null; return null;
} }
public void reserveManaSourcesForMain2(SpellAbility sa) { public void reserveManaSources(SpellAbility sa) {
reserveManaSources(sa, PhaseType.MAIN2, false);
}
public void reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player); CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
AiCardMemory.MemorySet memSet;
switch (phaseType) {
case MAIN2:
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
break;
case COMBAT_DECLARE_BLOCKERS:
memSet = enemy ? AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK
: AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK;
break;
default:
System.out.println("Warning: unsupported mana reservation phase specified for reserveManaSources: "
+ phaseType.name() + ", reserving until Main 2 instead. Consider adding support for the phase if needed.");
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
break;
}
for (Card c : manaSources) { for (Card c : manaSources) {
AiCardMemory.rememberCard(player, c, AiCardMemory.MemorySet.HELD_MANA_SOURCES); AiCardMemory.rememberCard(player, c, memSet);
} }
} }
@@ -666,6 +677,11 @@ public class AiController {
if (sa instanceof SpellPermanent) { if (sa instanceof SpellPermanent) {
return canPlayFromEffectAI((SpellPermanent)sa, false, true); return canPlayFromEffectAI((SpellPermanent)sa, false, true);
} }
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
return AiPlayDecision.TargetingFailed;
}
}
if (sa instanceof Spell) { if (sa instanceof Spell) {
if (ComputerUtil.getDamageForPlaying(player, sa) >= player.getLife() if (ComputerUtil.getDamageForPlaying(player, sa) >= player.getLife()
&& !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) { && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
@@ -676,44 +692,36 @@ public class AiController {
return AiPlayDecision.WillPlay; return AiPlayDecision.WillPlay;
} }
private AiPlayDecision canPlaySpellBasic(final Card card, final SpellAbility sa) { public boolean isNonDisabledCardInPlay(final String cardName) {
boolean isRightSplit = sa != null && sa.isRightSplit(); for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay"; if (card.getName().equals(cardName)) {
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar": "NeedsToPlayVar"; // TODO - Better logic to detemine if a permanent is disabled by local effects
// currently assuming any permanent enchanted by another player
if (card.hasSVar(needsToPlayName)) { // is disabled and a second copy is necessary
final String needsToPlay = card.getSVar(needsToPlayName); // will need actual logic that determines if the enchantment is able
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield); // to disable the permanent or it's still functional and a duplicate is unneeded.
boolean disabledByEnemy = false;
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, null); for (Card card2 : card.getEnchantedBy(false)) {
if (list.isEmpty()) { if (card2.getOwner() != player) {
return AiPlayDecision.MissingNeededCards; disabledByEnemy = true;
}
}
if (!disabledByEnemy) {
return true;
}
} }
} }
if (card.getSVar(needsToPlayVarName).length() > 0) { return false;
final String needsToPlay = card.getSVar(needsToPlayVarName);
int x = 0;
int y = 0;
String sVar = needsToPlay.split(" ")[0];
String comparator = needsToPlay.split(" ")[1];
String compareTo = comparator.substring(2);
try {
x = Integer.parseInt(sVar);
} catch (final NumberFormatException e) {
x = CardFactoryUtil.xCount(card, card.getSVar(sVar));
}
try {
y = Integer.parseInt(compareTo);
} catch (final NumberFormatException e) {
y = CardFactoryUtil.xCount(card, card.getSVar(compareTo));
}
if (!Expressions.compare(x, comparator, y)) {
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
}
}
return AiPlayDecision.WillPlay;
} }
private AiPlayDecision canPlaySpellBasic(final Card card, final SpellAbility sa) {
if ("True".equals(card.getSVar("NonStackingEffect")) && isNonDisabledCardInPlay(card.getName())) {
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
}
// add any other necessary logic to play a basic spell here
return ComputerUtilCard.checkNeedsToPlayReqs(card, sa);
}
// not sure "playing biggest spell" matters? // not sure "playing biggest spell" matters?
private final static Comparator<SpellAbility> saComparator = new Comparator<SpellAbility>() { private final static Comparator<SpellAbility> saComparator = new Comparator<SpellAbility>() {
@Override @Override
@@ -724,12 +732,31 @@ public class AiController {
int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC(); int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC();
// deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True // deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True
if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard() != null && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
return 1; return 1;
} else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { } else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard() != null && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
return -1; return -1;
} }
// deprioritize pump spells with pure energy cost (can be activated last,
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
int a2 = 0, b2 = 0;
if (a.getApi() == ApiType.Pump && a.getPayCosts() != null && a.getPayCosts().getCostEnergy() != null) {
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
a2 = a.getPayCosts().getCostEnergy().convertAmount();
}
}
if (b.getApi() == ApiType.Pump && b.getPayCosts() != null && b.getPayCosts().getCostEnergy() != null) {
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
b2 = b.getPayCosts().getCostEnergy().convertAmount();
}
}
if (a2 == 0 && b2 > 0) {
return -1;
} else if (b2 == 0 && a2 > 0) {
return 1;
}
// cast 0 mana cost spells first (might be a Mox) // cast 0 mana cost spells first (might be a Mox)
if (a1 == 0 && b1 > 0 && ApiType.Mana != a.getApi()) { if (a1 == 0 && b1 > 0 && ApiType.Mana != a.getApi()) {
return -1; return -1;
@@ -737,9 +764,9 @@ public class AiController {
return 1; return 1;
} }
if (a.getHostCard().hasSVar("FreeSpellAI")) { if (a.getHostCard() != null && a.getHostCard().hasSVar("FreeSpellAI")) {
return -1; return -1;
} else if (b.getHostCard().hasSVar("FreeSpellAI")) { } else if (b.getHostCard() != null && b.getHostCard().hasSVar("FreeSpellAI")) {
return 1; return 1;
} }
@@ -752,35 +779,42 @@ public class AiController {
private int getSpellAbilityPriority(SpellAbility sa) { private int getSpellAbilityPriority(SpellAbility sa) {
int p = 0; int p = 0;
Card source = sa.getHostCard(); Card source = sa.getHostCard();
final Player ai = source.getController(); final Player ai = source == null ? sa.getActivatingPlayer() : source.getController();
if (ai == null) {
System.err.println("Error: couldn't figure out the activating player and host card for SA: " + sa);
return 0;
}
final boolean noCreatures = ai.getCreaturesInPlay().isEmpty(); final boolean noCreatures = ai.getCreaturesInPlay().isEmpty();
// puts creatures in front of spells
if (source.isCreature()) { if (source != null) {
p += 1; // puts creatures in front of spells
} if (source.isCreature()) {
// don't play equipments before having any creatures p += 1;
if (source.isEquipment() && noCreatures) { }
p -= 9; // don't play equipments before having any creatures
} if (source.isEquipment() && noCreatures) {
// use Surge and Prowl costs when able to p -= 9;
if (sa.isSurged() || }
(sa.getRestrictions().getProwlTypes() != null && !sa.getRestrictions().getProwlTypes().isEmpty())) { // 1. increase chance of using Surge effects
p += 9; // 2. non-surged versions are usually inefficient
} if (source.getOracleText().contains("surge cost") && !sa.isSurged()) {
// 1. increase chance of using Surge effects p -= 9;
// 2. non-surged versions are usually inefficient }
if (sa.getHostCard().getOracleText().contains("surge cost") && !sa.isSurged()) { // move snap-casted spells to front
p -= 9; if (source.isInZone(ZoneType.Graveyard)) {
} if (sa.getMayPlay() != null && source.mayPlay(sa.getMayPlay()) != null) {
// move snap-casted spells to front p += 50;
if (source.isInZone(ZoneType.Graveyard)) { }
if(sa.getMayPlay() != null && source.mayPlay(sa.getMayPlay()) != null) { }
p += 50; // if the profile specifies it, deprioritize Storm spells in an attempt to build up storm count
if (source.hasKeyword(Keyword.STORM) && ai.getController() instanceof PlayerControllerAi) {
p -= (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.PRIORITY_REDUCTION_FOR_STORM_SPELLS));
} }
} }
// artifacts and enchantments with effects that do not stack
if ("True".equals(source.getSVar("NonStackingEffect")) && ai.isCardInPlay(source.getName())) { // use Surge and Prowl costs when able to
p -= 9; if (sa.isSurged() || sa.isProwl()) {
p += 9;
} }
// sort planeswalker abilities with most costly first // sort planeswalker abilities with most costly first
if (sa.getRestrictions().isPwAbility()) { if (sa.getRestrictions().isPwAbility()) {
@@ -801,11 +835,6 @@ public class AiController {
p -= 9; p -= 9;
} }
// if the profile specifies it, deprioritize Storm spells in an attempt to build up storm count
if (source.hasKeyword("Storm") && ai.getController() instanceof PlayerControllerAi) {
p -= (((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.PRIORITY_REDUCTION_FOR_STORM_SPELLS));
}
// try to cast mana ritual spells before casting spells to maximize potential mana // try to cast mana ritual spells before casting spells to maximize potential mana
if ("ManaRitual".equals(sa.getParam("AILogic"))) { if ("ManaRitual".equals(sa.getParam("AILogic"))) {
p += 9; p += 9;
@@ -840,7 +869,36 @@ public class AiController {
sourceCard = sa.getHostCard(); sourceCard = sa.getHostCard();
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) { if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
min = 1; min = 1;
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
} }
if (sa.hasParam("AnyNumber")) {
if ("DiscardUncastableAndExcess".equals(sa.getParam("AILogic"))) {
CardCollection discards = new CardCollection();
final CardCollectionView inHand = player.getCardsIn(ZoneType.Hand);
final int numLandsOTB = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
int numOppInHand = 0;
for (Player p : player.getGame().getPlayers()) {
if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
numOppInHand = p.getCardsIn(ZoneType.Hand).size();
}
}
for (Card c : inHand) {
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), player)) {
discards.add(c);
}
if ((c.isLand() && numLandsOTB >= 5) || (c.getFirstSpellAbility() != null && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getFirstSpellAbility(), player))) {
if (discards.size() + 1 <= numOppInHand) {
discards.add(c);
}
}
}
return discards;
}
}
} }
// look for good discards // look for good discards
@@ -858,6 +916,9 @@ public class AiController {
} }
if (prefCard == null) { if (prefCard == null) {
prefCard = ComputerUtil.getCardPreference(player, sourceCard, "DiscardCost", validCards); prefCard = ComputerUtil.getCardPreference(player, sourceCard, "DiscardCost", validCards);
if (prefCard != null && prefCard.hasSVar("DoNotDiscardIfAble")) {
prefCard = null;
}
} }
if (prefCard != null) { if (prefCard != null) {
discardList.add(prefCard); discardList.add(prefCard);
@@ -894,13 +955,52 @@ public class AiController {
if (numLandsInHand > 0) { if (numLandsInHand > 0) {
numLandsAvailable++; numLandsAvailable++;
} }
//Discard unplayable card //Discard unplayable card
if (validCards.get(0).getCMC() > numLandsAvailable) { boolean discardedUnplayable = false;
discardList.add(validCards.get(0)); for (int j = 0; j < validCards.size(); j++) {
validCards.remove(validCards.get(0)); if (validCards.get(j).getCMC() > numLandsAvailable && !validCards.get(j).hasSVar("DoNotDiscardIfAble")) {
discardList.add(validCards.get(j));
validCards.remove(validCards.get(j));
discardedUnplayable = true;
break;
} else if (validCards.get(j).getCMC() <= numLandsAvailable) {
// cut short to avoid looping over cards which are guaranteed not to fit the criteria
break;
}
} }
else { //Discard worst card
if (!discardedUnplayable) {
// discard worst card
Card worst = ComputerUtilCard.getWorstAI(validCards); Card worst = ComputerUtilCard.getWorstAI(validCards);
if (worst == null) {
// there were only instants and sorceries, and maybe cards that are not good to discard, so look
// for more discard options
worst = ComputerUtilCard.getCheapestSpellAI(validCards);
}
if (worst == null && !validCards.isEmpty()) {
// still nothing chosen, so choose the first thing that works, trying not to make DoNotDiscardIfAble
// discards
for (Card c : validCards) {
if (!c.hasSVar("DoNotDiscardIfAble")) {
worst = c;
break;
}
}
// Only DoNotDiscardIfAble cards? If we have a duplicate for something, discard it
if (worst == null) {
for (Card c : validCards) {
if (CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(c.getName())).size() > 1) {
worst = c;
break;
}
}
if (worst == null) {
// Otherwise just grab a random card and discard it
worst = Aggregates.random(validCards);
}
}
}
discardList.add(worst); discardList.add(worst);
validCards.remove(worst); validCards.remove(worst);
} }
@@ -1059,17 +1159,46 @@ public class AiController {
} }
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player); CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
CardCollection playBeforeLand = CardLists.filter(
player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop")
);
if (!playBeforeLand.isEmpty()) {
SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList(
ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false
);
if (wantToPlayBeforeLand != null) {
return singleSpellAbilityList(wantToPlayBeforeLand);
}
}
if (landsWannaPlay != null) { if (landsWannaPlay != null) {
landsWannaPlay = filterLandsToPlay(landsWannaPlay); landsWannaPlay = filterLandsToPlay(landsWannaPlay);
Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi); Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi);
if (landsWannaPlay != null && !landsWannaPlay.isEmpty() && player.canPlayLand(null)) { if (landsWannaPlay != null && !landsWannaPlay.isEmpty()) {
// TODO search for other land it might want to play?
Card land = chooseBestLandToPlay(landsWannaPlay); Card land = chooseBestLandToPlay(landsWannaPlay);
if (ComputerUtil.getDamageFromETB(player, land) < player.getLife() || !player.canLoseLife() if (ComputerUtil.getDamageFromETB(player, land) < player.getLife() || !player.canLoseLife()
|| player.cantLoseForZeroOrLessLife() ) { || player.cantLoseForZeroOrLessLife() ) {
game.PLAY_LAND_SURROGATE.setHostCard(land); if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
final List<SpellAbility> abilities = Lists.newArrayList(); final List<SpellAbility> abilities = Lists.newArrayList();
abilities.add(game.PLAY_LAND_SURROGATE);
return abilities; LandAbility la = new LandAbility(land, player, null);
if (la.canPlay()) {
abilities.add(la);
}
// add mayPlay option
for (CardPlayOption o : land.mayPlay(player)) {
la = new LandAbility(land, player, o.getAbility());
if (la.canPlay()) {
abilities.add(la);
}
}
if (!abilities.isEmpty()) {
return abilities;
}
}
} }
} }
} }
@@ -1077,6 +1206,116 @@ public class AiController {
return singleSpellAbilityList(getSpellAbilityToPlay()); return singleSpellAbilityList(getSpellAbilityToPlay());
} }
private boolean isSafeToHoldLandDropForMain2(Card landToPlay) {
boolean hasMomir = !CardLists.filter(player.getCardsIn(ZoneType.Command),
CardPredicates.nameEquals("Momir Vig, Simic Visionary Avatar")).isEmpty();
if (hasMomir) {
// Don't do this in Momir variants since it messes with the AI decision making for the avatar.
return false;
}
if (!MyRandom.percentTrue(getIntProperty(AiProps.HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED))) {
// check against the chance specified in the profile
return false;
}
if (game.getPhaseHandler().getTurn() <= 2) {
// too obvious when doing it on the very first turn of the game
return false;
}
CardCollection inHand = CardLists.filter(player.getCardsIn(ZoneType.Hand),
Predicates.not(CardPredicates.Presets.LANDS));
CardCollectionView otb = player.getCardsIn(ZoneType.Battlefield);
if (getBooleanProperty(AiProps.HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS)) {
if (CardLists.filter(otb, Predicates.not(CardPredicates.Presets.LANDS)).isEmpty()) {
return false;
}
}
// TODO: improve the detection of taplands
boolean isTapLand = false;
for (ReplacementEffect repl : landToPlay.getReplacementEffects()) {
if (repl.getParamOrDefault("Description", "").equals("CARDNAME enters the battlefield tapped.")) {
isTapLand = true;
}
}
int totalCMCInHand = Aggregates.sum(inHand, CardPredicates.Accessors.fnGetCmc);
int minCMCInHand = Aggregates.min(inHand, CardPredicates.Accessors.fnGetCmc);
int predictedMana = ComputerUtilMana.getAvailableManaEstimate(player, true);
boolean canCastWithLandDrop = (predictedMana + 1 >= minCMCInHand) && !isTapLand;
boolean cantCastAnythingNow = predictedMana < minCMCInHand;
boolean hasRelevantAbsOTB = !CardLists.filter(otb, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
boolean isTapLand = false;
for (ReplacementEffect repl : card.getReplacementEffects()) {
// TODO: improve the detection of taplands
if (repl.getParamOrDefault("Description", "").equals("CARDNAME enters the battlefield tapped.")) {
isTapLand = true;
}
}
for (SpellAbility sa : card.getSpellAbilities()) {
if (sa.getPayCosts() != null && sa.isAbility()
&& sa.getPayCosts().getCostMana() != null
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)
&& (!sa.hasParam("ActivationZone") || sa.getParam("ActivationZone").contains("Battlefield"))) {
return true;
}
}
return false;
}
}).isEmpty();
boolean hasLandBasedEffect = !CardLists.filter(otb, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
for (Trigger t : card.getTriggers()) {
Map<String, String> params = t.getMapParams();
if ("ChangesZone".equals(params.get("Mode"))
&& params.containsKey("ValidCard")
&& !params.get("ValidCard").contains("nonLand")
&& ((params.get("ValidCard").contains("Land")) || (params.get("ValidCard").contains("Permanent")))
&& "Battlefield".equals(params.get("Destination"))) {
// Landfall and other similar triggers
return true;
}
}
for (String sv : card.getSVars().keySet()) {
String varValue = card.getSVar(sv);
if (varValue.startsWith("Count$Valid") || sv.equals("BuffedBy")) {
if (varValue.contains("Land") || varValue.contains("Plains") || varValue.contains("Forest")
|| varValue.contains("Mountain") || varValue.contains("Island") || varValue.contains("Swamp")
|| varValue.contains("Wastes")) {
// In presence of various cards that get buffs like "equal to the number of lands you control",
// safer for our AI model to just play the land earlier rather than make a blunder
return true;
}
}
}
return false;
}
}).isEmpty();
// TODO: add prediction for effects that will untap a tapland as it enters the battlefield
if (!canCastWithLandDrop && cantCastAnythingNow && !hasLandBasedEffect && (!hasRelevantAbsOTB || isTapLand)) {
// Hopefully there's not much to do with the extra mana immediately, can wait for Main 2
return true;
}
if ((predictedMana <= totalCMCInHand && canCastWithLandDrop) || (hasRelevantAbsOTB && !isTapLand) || hasLandBasedEffect) {
// Might need an extra land to cast something, or for some kind of an ETB ability with a cost or an
// alternative cost (if we cast it in Main 1), or to use an activated ability on the battlefield
return false;
}
return true;
}
private final SpellAbility getSpellAbilityToPlay() { private final SpellAbility getSpellAbilityToPlay() {
// if top of stack is owned by me // if top of stack is owned by me
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) { if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) {
@@ -1109,7 +1348,7 @@ public class AiController {
continue; continue;
} }
if (sa.getHostCard().hasKeyword("Storm") if (sa.getHostCard().hasKeyword(Keyword.STORM)
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell && sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
&& CardLists.filter(player.getCardsIn(ZoneType.Hand), Predicates.not(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.hasKeyword("Storm")))).size() > 0) { && CardLists.filter(player.getCardsIn(ZoneType.Hand), Predicates.not(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.hasKeyword("Storm")))).size() > 0) {
if (game.getView().getStormCount() < this.getIntProperty(AiProps.MIN_COUNT_FOR_STORM_SPELLS)) { if (game.getView().getStormCount() < this.getIntProperty(AiProps.MIN_COUNT_FOR_STORM_SPELLS)) {
@@ -1245,23 +1484,25 @@ public class AiController {
boolean hasLeyline1 = false; boolean hasLeyline1 = false;
SpellAbility saGemstones = null; SpellAbility saGemstones = null;
for(int i = 0; i < result.size(); i++) { List<SpellAbility> toRemove = Lists.newArrayList();
SpellAbility sa = result.get(i); for(SpellAbility sa : result) {
String srcName = sa.getHostCard().getName(); String srcName = sa.getHostCard().getName();
if ("Gemstone Caverns".equals(srcName)) { if ("Gemstone Caverns".equals(srcName)) {
if (saGemstones == null) if (saGemstones == null)
saGemstones = sa; saGemstones = sa;
else else
result.remove(i--); toRemove.add(sa);
} else if ("Leyline of Singularity".equals(srcName)) { } else if ("Leyline of Singularity".equals(srcName)) {
if (!hasLeyline1) if (!hasLeyline1)
hasLeyline1 = true; hasLeyline1 = true;
else else
result.remove(i--); toRemove.add(sa);
} }
} }
for(SpellAbility sa : toRemove) {
result.remove(sa);
}
// Play them last // Play them last
if (saGemstones != null) { if (saGemstones != null) {
@@ -1306,7 +1547,7 @@ public class AiController {
+ MyRandom.getRandom().nextInt(3); + MyRandom.getRandom().nextInt(3);
return Math.max(remaining, min) / 2; return Math.max(remaining, min) / 2;
} else if ("LowestLoseLife".equals(logic)) { } 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)) { } else if ("HighestGetCounter".equals(logic)) {
return MyRandom.getRandom().nextInt(3); return MyRandom.getRandom().nextInt(3);
} else if (source.hasSVar("EnergyToPay")) { } else if (source.hasSVar("EnergyToPay")) {
@@ -1337,11 +1578,11 @@ public class AiController {
// and exaclty one counter of the specifice type gets high priority to keep the card // and exaclty one counter of the specifice type gets high priority to keep the card
if (allies.contains(crd.getController())) { if (allies.contains(crd.getController())) {
// except if its a Chronozoa, because it WANTS to be removed to make more // except if its a Chronozoa, because it WANTS to be removed to make more
if (crd.hasKeyword("Vanishing") && !"Chronozoa".equals(crd.getName())) { if (crd.hasKeyword(Keyword.VANISHING) && !"Chronozoa".equals(crd.getName())) {
if (crd.getCounters(CounterType.TIME) == 1) { if (crd.getCounters(CounterType.TIME) == 1) {
return CounterType.TIME; return CounterType.TIME;
} }
} else if (crd.hasKeyword("Fading")) { } else if (crd.hasKeyword(Keyword.FADING)) {
if (crd.getCounters(CounterType.FADE) == 1) { if (crd.getCounters(CounterType.FADE) == 1) {
return CounterType.FADE; return CounterType.FADE;
} }
@@ -1428,6 +1669,24 @@ public class AiController {
} else { } else {
break; 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;
}
}
} }
} }
@@ -1553,7 +1812,11 @@ public class AiController {
if (useSimulation) { if (useSimulation) {
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
} }
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); if (sa.getApi() == ApiType.Explore) {
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
} else {
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
}
} }
public List<SpellAbility> orderPlaySa(List<SpellAbility> activePlayerSAs) { public List<SpellAbility> orderPlaySa(List<SpellAbility> activePlayerSAs) {
@@ -1575,6 +1838,10 @@ public class AiController {
List<SpellAbility> evolve = filterList(putCounter, SpellAbilityPredicates.hasParam("Evolve")); List<SpellAbility> evolve = filterList(putCounter, SpellAbilityPredicates.hasParam("Evolve"));
List<SpellAbility> token = filterListByApi(activePlayerSAs, ApiType.Token);
List<SpellAbility> pump = filterListByApi(activePlayerSAs, ApiType.Pump);
List<SpellAbility> pumpAll = filterListByApi(activePlayerSAs, ApiType.PumpAll);
// do mandatory discard early if hand is empty or has DiscardMe card // do mandatory discard early if hand is empty or has DiscardMe card
boolean discardEarly = false; boolean discardEarly = false;
CardCollectionView playerHand = player.getCardsIn(ZoneType.Hand); CardCollectionView playerHand = player.getCardsIn(ZoneType.Hand);
@@ -1583,6 +1850,11 @@ public class AiController {
result.addAll(mandatoryDiscard); result.addAll(mandatoryDiscard);
} }
// token should be added first so they might get the pump bonus
result.addAll(token);
result.addAll(pump);
result.addAll(pumpAll);
// do Evolve Trigger before other PutCounter SpellAbilities // do Evolve Trigger before other PutCounter SpellAbilities
// do putCounter before Draw/Discard because it can cause a Draw Trigger // do putCounter before Draw/Discard because it can cause a Draw Trigger
result.addAll(evolve); result.addAll(evolve);
@@ -1598,6 +1870,9 @@ public class AiController {
} }
result.addAll(activePlayerSAs); result.addAll(activePlayerSAs);
//need to reverse because of magic stack
Collections.reverse(result);
return result; return result;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,21 @@
package forge.ai; package forge.ai;
public enum AiPlayDecision { public enum AiPlayDecision {
WillPlay, WillPlay,
CantPlaySa, CantPlaySa,
CantPlayAi, CantPlayAi,
CantAfford, CantAfford,
CantAffordX, CantAffordX,
WaitForMain2, WaitForMain2,
AnotherTime, AnotherTime,
MissingNeededCards, MissingNeededCards,
NeedsToPlayCriteriaNotMet, NeedsToPlayCriteriaNotMet,
TargetingFailed, TargetingFailed,
CostNotAcceptable, CostNotAcceptable,
WouldDestroyLegend, WouldDestroyLegend,
WouldDestroyOtherPlaneswalker, WouldDestroyOtherPlaneswalker,
WouldBecomeZeroToughnessCreature, WouldBecomeZeroToughnessCreature,
WouldDestroyWorldEnchantment, WouldDestroyWorldEnchantment,
BadEtbEffects, BadEtbEffects,
CurseEffects; CurseEffects;
} }

View File

@@ -21,6 +21,7 @@ import forge.LobbyPlayer;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.FileUtil; import forge.util.FileUtil;
import forge.util.TextUtil;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import java.io.File; import java.io.File;
@@ -52,7 +53,7 @@ public class AiProfileUtil {
* @return the full relative path and file name for the given profile. * @return the full relative path and file name for the given profile.
*/ */
private static String buildFileName(final String profileName) { private static String buildFileName(final String profileName) {
return String.format("%s/%s%s", AI_PROFILE_DIR, profileName, AI_PROFILE_EXT); return TextUtil.concatNoSpace(AI_PROFILE_DIR, "/", profileName, AI_PROFILE_EXT);
} }
/** /**

View File

@@ -1,67 +1,129 @@
/* /*
* Forge: Play Magic: the Gathering. * Forge: Play Magic: the Gathering.
* Copyright (C) 2013 Forge Team * Copyright (C) 2013 Forge Team
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package forge.ai; package forge.ai;
/** /**
* AI personality profile settings identifiers, and their default values. * AI personality profile settings identifiers, and their default values.
* When this class is instantiated, these enum values are used * When this class is instantiated, these enum values are used
* in a map that is populated with the current AI profile settings * in a map that is populated with the current AI profile settings
* from the text file. * from the text file.
*/ */
public enum AiProps { /** */ public enum AiProps { /** */
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */ DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */ DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */ DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
MULLIGAN_THRESHOLD ("5"), /** */ MULLIGAN_THRESHOLD ("5"), /** */
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"), PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
CHEAT_WITH_MANA_ON_SHUFFLE ("false"), HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED ("0"), /** */
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"), HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS ("true"), /** */
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"), CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"), MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */ MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */ PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
PLAY_AGGRO ("false"), /** */ PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
MIN_SPELL_CMC_TO_COUNTER ("0"), RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
ALWAYS_COUNTER_OTHER_COUNTERSPELLS ("true"), /** */ PLAY_AGGRO ("false"),
ALWAYS_COUNTER_DAMAGE_SPELLS ("true"), /** */ CHANCE_TO_ATTACK_INTO_TRADE ("40"), /** */
ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS ("true"), /** */ RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE ("true"), /** */
ALWAYS_COUNTER_REMOVAL_SPELLS ("true"), /** */ ATTACK_INTO_TRADE_WHEN_TAPPED_OUT ("false"), /** */
ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */ CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA ("0"), /** */
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("false"), /** */ TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK ("true"), /** */
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */ TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("false"), /** */
USE_BERSERK_AGGRESSIVELY ("false"), /** */ CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("30"), /** */
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */ ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK ("true"), /** */
STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE ("1"), /** */ RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS ("false"), /** */
STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK ("3"), /** */ MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE ("1"), /** */
STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK ("6"), /** */ ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT ("true"), /** */
STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING ("3"), /** */ MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL ("1"), /** */
STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP ("false"); /** */ MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("30"), /** */
MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("70"), /** */
private final String strDefaultVal; CHANCE_DECREASE_TO_TRADE_VS_EMBALM ("30"), /** */
CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER ("70"), /** */
/** @param s0 &emsp; {@link java.lang.String} */ CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER ("0"), /** */
AiProps(final String s0) { THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER ("135"), /** */
this.strDefaultVal = s0; THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER ("110"), /** */
} CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL ("true"), /** */
MIN_SPELL_CMC_TO_COUNTER ("0"), /** */
/** @return {@link java.lang.String} */ CHANCE_TO_COUNTER_CMC_1 ("50"), /** */
public String getDefault() { CHANCE_TO_COUNTER_CMC_2 ("75"), /** */
return strDefaultVal; CHANCE_TO_COUNTER_CMC_3 ("100"), /** */
} ALWAYS_COUNTER_OTHER_COUNTERSPELLS ("true"), /** */
} ALWAYS_COUNTER_DAMAGE_SPELLS ("true"), /** */
ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS ("true"), /** */
ALWAYS_COUNTER_REMOVAL_SPELLS ("true"), /** */
ALWAYS_COUNTER_PUMP_SPELLS ("true"), /** */
ALWAYS_COUNTER_AURAS ("true"), /** */
ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
USE_BERSERK_AGGRESSIVELY ("false"), /** */
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE ("1"), /** */
STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK ("3"), /** */
STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK ("6"), /** */
STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING ("3"), /** */
STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP ("false"),
TOKEN_GENERATION_ABILITY_CHANCE ("80"), /** */
TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER ("true"), /** */
TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS ("true"), /** */
SCRY_NUM_LANDS_TO_STILL_NEED_MORE ("4"), /** */
SCRY_NUM_LANDS_TO_NOT_NEED_MORE ("7"), /** */
SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES ("4"), /** */
SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC ("3"), /** */
SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE ("160"), /** */
SCRY_EVALTHR_CMC_THRESHOLD ("3"), /** */
SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM ("false"), /** */
SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF ("1"), /** */
SURVEIL_NUM_CARDS_IN_LIBRARY_TO_BAIL ("10"), /** */
COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION ("true"), /** */
COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION ("true"), /** */
CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT ("true"), /** */
CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY ("true"), /** */
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF ("3"), /** */
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF ("3"), /** */
INTUITION_ALTERNATIVE_LOGIC ("false"), /** */
EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD ("2"),
EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE("2"), /** */
MOJHOSTO_NUM_LANDS_TO_ACTIVATE_JHOIRA("5"), /** */
MOJHOSTO_CHANCE_TO_PREFER_JHOIRA_OVER_MOMIR ("50"), /** */
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT ("20"), /** */
// Experimental features, must be removed after extensive testing and, ideally, defaulting
// <-- There are no experimental options here -->
AI_IN_DANGER_THRESHOLD("4"),
AI_IN_DANGER_MAX_THRESHOLD("4");
private final String strDefaultVal;
/** @param s0 &emsp; {@link java.lang.String} */
AiProps(final String s0) {
this.strDefaultVal = s0;
}
/** @return {@link java.lang.String} */
public String getDefault() {
return strDefaultVal;
}
}

View File

@@ -17,23 +17,9 @@
*/ */
package forge.ai; package forge.ai;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList; import com.google.common.collect.*;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import forge.ai.ability.ProtectAi; import forge.ai.ability.ProtectAi;
import forge.ai.ability.TokenAi; import forge.ai.ability.TokenAi;
import forge.card.CardType; import forge.card.CardType;
@@ -46,32 +32,18 @@ import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect; import forge.game.ability.effects.CharmEffect;
import forge.game.card.Card; import forge.game.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.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.Cost; import forge.game.cost.*;
import forge.game.cost.CostDiscard; import forge.game.keyword.Keyword;
import forge.game.cost.CostPart;
import forge.game.cost.CostPayment;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostSacrifice;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AbilityManaPart; import forge.game.spellability.*;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
@@ -79,7 +51,11 @@ import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.TextUtil;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
/** /**
@@ -99,14 +75,28 @@ public class ComputerUtil {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
if (sa.isSpell() && !source.isCopiedSpell()) { if (sa.isSpell() && !source.isCopiedSpell()) {
if (source.getType().hasStringType("Arcane")) {
sa = AbilityUtils.addSpliceEffects(sa);
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty() && ai.getController().isAI()) {
// we need to reconsider and retarget the SA after additional SAs have been added onto it via splice,
// otherwise the AI will fail to add the card to stack and that'll knock it out of the game
sa.resetTargets();
if (((PlayerControllerAi) ai.getController()).getAi().canPlaySa(sa) != AiPlayDecision.WillPlay) {
// for whatever reason the AI doesn't want to play the thing with the spliced subs anymore,
// proceeding past this point may result in an illegal play
return false;
}
}
}
source.setCastSA(sa); source.setCastSA(sa);
sa.setLastStateBattlefield(game.getLastStateBattlefield()); sa.setLastStateBattlefield(game.getLastStateBattlefield());
sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setHostCard(game.getAction().moveToStack(source, sa)); sa.setHostCard(game.getAction().moveToStack(source, sa));
}
if (source.getType().hasStringType("Arcane")) { if (sa.isCopied()) {
sa = AbilityUtils.addSpliceEffects(sa); sa.resetPaidHash();
}
} }
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
@@ -192,7 +182,7 @@ public class ComputerUtil {
if (unless != null && !unless.endsWith(">")) { if (unless != null && !unless.endsWith(">")) {
final int amount = AbilityUtils.calculateAmount(source, unless, sa); final int amount = AbilityUtils.calculateAmount(source, unless, sa);
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size(); final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtil.getOpponentFor(ai), true).size();
// If the Unless isn't enough, this should be less likely to be used // If the Unless isn't enough, this should be less likely to be used
if (amount > usableManaSources) { if (amount > usableManaSources) {
@@ -272,6 +262,10 @@ public class ComputerUtil {
sa.setLastStateBattlefield(game.getLastStateBattlefield()); sa.setLastStateBattlefield(game.getLastStateBattlefield());
sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setLastStateGraveyard(game.getLastStateGraveyard());
newSA.setHostCard(game.getAction().moveToStack(source, sa)); newSA.setHostCard(game.getAction().moveToStack(source, sa));
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
CharmEffect.makeChoices(newSA);
}
} }
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA); final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
@@ -310,43 +304,46 @@ public class ComputerUtil {
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) { public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) {
final Game game = ai.getGame(); final Game game = ai.getGame();
String prefDef = "";
if (activate != null) { if (activate != null) {
prefDef = activate.getSVar("AIPreference");
final String[] prefGroups = activate.getSVar("AIPreference").split("\\|"); final String[] prefGroups = activate.getSVar("AIPreference").split("\\|");
for (String prefGroup : prefGroups) { for (String prefGroup : prefGroups) {
final String[] prefValid = prefGroup.trim().split("\\$"); final String[] prefValid = prefGroup.trim().split("\\$");
if (prefValid[0].equals(pref)) { if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) {
final CardCollection prefList = CardLists.getValidCards(typeList, prefValid[1].split(","), activate.getController(), activate, null);
CardCollection overrideList = null; CardCollection overrideList = null;
if (activate.hasSVar("AIPreferenceOverride")) { if (activate.hasSVar("AIPreferenceOverride")) {
overrideList = CardLists.getValidCards(typeList, activate.getSVar("AIPreferenceOverride"), activate.getController(), activate, null); overrideList = CardLists.getValidCards(typeList, activate.getSVar("AIPreferenceOverride"), activate.getController(), activate, null);
} }
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold"); for (String validItem : prefValid[1].split(",")) {
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold"); final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null);
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold");
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold");
if (threshold != -1) { if (threshold != -1) {
List<Card> toRemove = Lists.newArrayList(); List<Card> toRemove = Lists.newArrayList();
for (Card c : prefList) { for (Card c : prefList) {
if (c.isCreature()) { if (c.isCreature()) {
if (ComputerUtilCard.isUselessCreature(ai, c) || ComputerUtilCard.evaluateCreature(c) <= threshold) { if (ComputerUtilCard.isUselessCreature(ai, c) || ComputerUtilCard.evaluateCreature(c) <= threshold) {
continue; continue;
} else if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) { } else if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
continue; continue;
}
toRemove.add(c);
} }
toRemove.add(c); }
prefList.removeAll(toRemove);
}
if (minNeeded != -1) {
if (prefList.size() < minNeeded) {
return null;
} }
} }
prefList.removeAll(toRemove);
}
if (minNeeded != -1) {
if (prefList.size() < minNeeded) {
return null;
}
}
if (!prefList.isEmpty()) { if (!prefList.isEmpty() || (overrideList != null && !overrideList.isEmpty())) {
return ComputerUtilCard.getWorstAI(overrideList == null ? prefList : overrideList); return ComputerUtilCard.getWorstAI(overrideList == null ? prefList : overrideList);
}
} }
} }
} }
@@ -411,6 +408,11 @@ public class ComputerUtil {
} }
} }
// Survival of the Fittest logic
if (prefDef.contains("DiscardCost$Special:SurvivalOfTheFittest")) {
return SpecialCardAi.SurvivalOfTheFittest.considerDiscardTarget(ai);
}
// Discard lands // Discard lands
final CardCollection landsInHand = CardLists.getType(typeList, "Land"); final CardCollection landsInHand = CardLists.getType(typeList, "Land");
if (!landsInHand.isEmpty()) { if (!landsInHand.isEmpty()) {
@@ -707,6 +709,8 @@ public class ComputerUtil {
return sacrificed; // sacrifice none return sacrificed; // sacrifice none
} }
} }
boolean exceptSelf = "ExceptSelf".equals(source.getParam("AILogic"));
boolean removedSelf = false;
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) { if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) {
if (source.hasParam("Exploit")) { if (source.hasParam("Exploit")) {
@@ -764,11 +768,22 @@ public class ComputerUtil {
final int max = Math.min(remaining.size(), amount); final int max = Math.min(remaining.size(), amount);
if (exceptSelf) {
removedSelf = remaining.remove(source.getHostCard());
}
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
Card c = chooseCardToSacrifice(remaining, ai, destroy); Card c = chooseCardToSacrifice(remaining, ai, destroy);
remaining.remove(c); remaining.remove(c);
sacrificed.add(c); if (c != null) {
sacrificed.add(c);
}
} }
if (sacrificed.isEmpty() && removedSelf) {
sacrificed.add(source.getHostCard());
}
return sacrificed; return sacrificed;
} }
@@ -781,7 +796,7 @@ public class ComputerUtil {
} }
if (destroy) { if (destroy) {
final CardCollection indestructibles = CardLists.getKeyword(remaining, "Indestructible"); final CardCollection indestructibles = CardLists.getKeyword(remaining, Keyword.INDESTRUCTIBLE);
if (!indestructibles.isEmpty()) { if (!indestructibles.isEmpty()) {
return indestructibles.get(0); return indestructibles.get(0);
} }
@@ -818,7 +833,7 @@ public class ComputerUtil {
} }
public static boolean canRegenerate(Player ai, final Card card) { public static boolean canRegenerate(Player ai, final Card card) {
if (card.hasKeyword("CARDNAME can't be regenerated.")) { if (!card.canBeShielded()) {
return false; return false;
} }
@@ -848,11 +863,11 @@ public class ComputerUtil {
continue; // Won't play ability continue; // Won't play ability
} }
if (!ComputerUtilCost.checkSacrificeCost(controller, abCost, c)) { if (!ComputerUtilCost.checkSacrificeCost(controller, abCost, c, sa)) {
continue; // Won't play ability continue; // Won't play ability
} }
if (!ComputerUtilCost.checkCreatureSacrificeCost(controller, abCost, c)) { if (!ComputerUtilCost.checkCreatureSacrificeCost(controller, abCost, c, sa)) {
continue; // Won't play ability continue; // Won't play ability
} }
} }
@@ -868,7 +883,7 @@ public class ComputerUtil {
} }
} catch (final Exception ex) { } catch (final Exception ex) {
throw new RuntimeException(String.format("There is an error in the card code for %s:%s", c.getName(), ex.getMessage()), ex); throw new RuntimeException(TextUtil.concatNoSpace("There is an error in the card code for ", c.getName(), ":", ex.getMessage()), ex);
} }
} }
} }
@@ -907,7 +922,7 @@ public class ComputerUtil {
} }
} }
} catch (final Exception ex) { } catch (final Exception ex) {
throw new RuntimeException(String.format("There is an error in the card code for %s:%s", c.getName(), ex.getMessage()), ex); throw new RuntimeException(TextUtil.concatNoSpace("There is an error in the card code for ", c.getName(), ":", ex.getMessage()), ex);
} }
} }
} }
@@ -917,32 +932,38 @@ public class ComputerUtil {
public static boolean castPermanentInMain1(final Player ai, final SpellAbility sa) { public static boolean castPermanentInMain1(final Player ai, final SpellAbility sa) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
if ("True".equals(card.getSVar("NonStackingEffect")) && card.getController().isCardInPlay(card.getName())) {
return false;
}
if (card.hasSVar("PlayMain1")) { if (card.hasSVar("PlayMain1")) {
if (card.getSVar("PlayMain1").equals("ALWAYS") || sa.getPayCosts().hasNoManaCost()) { if (card.getSVar("PlayMain1").equals("ALWAYS") || sa.getPayCosts().hasNoManaCost()) {
return true; return true;
} else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) { } else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) {
//Only play these main1 when the opponent has creatures (stealing and giving them haste) //Only play these main1 when the opponent has creatures (stealing and giving them haste)
if (!card.getController().getOpponent().getCreaturesInPlay().isEmpty()) { if (!ai.getOpponents().getCreaturesInPlay().isEmpty()) {
return true; return true;
} }
} else if (!card.getController().getCreaturesInPlay().isEmpty()) { } else if (!card.getController().getCreaturesInPlay().isEmpty()) {
return true; return true;
} }
} }
// try not to cast Raid creatures in main 1 if an attack is likely
if ("Count$AttackersDeclared".equals(card.getSVar("RaidTest")) && !card.hasKeyword(Keyword.HASTE)) {
for (Card potentialAtkr: ai.getCreaturesInPlay()) {
if (ComputerUtilCard.doesCreatureAttackAI(ai, potentialAtkr)) {
return false;
}
}
}
if (card.getManaCost().isZero()) { if (card.getManaCost().isZero()) {
return true; return true;
} }
if (card.isCreature() && !card.hasKeyword("Defender") && (card.hasKeyword("Haste") || ComputerUtil.hasACardGivingHaste(ai) || sa.isDash())) { if (card.isCreature() && !card.hasKeyword(Keyword.DEFENDER)
&& (card.hasKeyword(Keyword.HASTE) || ComputerUtil.hasACardGivingHaste(ai, true) || sa.isDash())) {
return true; return true;
} }
if (card.hasKeyword("Exalted")) { if (card.hasKeyword(Keyword.EXALTED)) {
return true; return true;
} }
@@ -973,27 +994,27 @@ public class ComputerUtil {
return true; 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; return true;
} }
if (card.isCreature()) { if (card.isCreature()) {
if (buffedcard.hasKeyword("Soulbond") && !buffedcard.isPaired()) { if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
return true; return true;
} }
if (buffedcard.hasKeyword("Evolve")) { if (buffedcard.hasKeyword(Keyword.EVOLVE)) {
if (buffedcard.getNetPower() < card.getNetPower() || buffedcard.getNetToughness() < card.getNetToughness()) { if (buffedcard.getNetPower() < card.getNetPower() || buffedcard.getNetToughness() < card.getNetToughness()) {
return true; return true;
} }
} }
} }
if (card.hasKeyword("Soulbond") && buffedcard.isCreature() && !buffedcard.isPaired()) { if (card.hasKeyword(Keyword.SOULBOND) && buffedcard.isCreature() && !buffedcard.isPaired()) {
return true; return true;
} }
} // BuffedBy } // BuffedBy
// get all cards the human controls with AntiBuffedBy // 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) { for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) { if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy"); final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1033,11 +1054,11 @@ public class ComputerUtil {
return ret; return ret;
} else { } else {
// Otherwise, if life is possibly in danger, then this is fine. // Otherwise, if life is possibly in danger, then this is fine.
Combat combat = new Combat(ai.getOpponent()); Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
CardCollectionView attackers = ai.getOpponent().getCreaturesInPlay(); CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
for (Card att : attackers) { for (Card att : attackers) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) { if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, att.getController().getOpponent()); combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
} }
} }
AiBlockController aiBlock = new AiBlockController(ai); AiBlockController aiBlock = new AiBlockController(ai);
@@ -1101,7 +1122,7 @@ public class ComputerUtil {
return (sa.getHostCard().isCreature() return (sa.getHostCard().isCreature()
&& sa.getPayCosts().hasTapCost() && sa.getPayCosts().hasTapCost()
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !ph.getNextTurn().equals(sa.getActivatingPlayer())) && !ph.getNextTurn().equals(sa.getActivatingPlayer()))
&& !sa.getHostCard().hasSVar("EndOfTurnLeavePlay") && !sa.getHostCard().hasSVar("EndOfTurnLeavePlay")
&& !sa.hasParam("ActivationPhases")); && !sa.hasParam("ActivationPhases"));
} }
@@ -1111,6 +1132,10 @@ public class ComputerUtil {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final SpellAbility sub = sa.getSubAbility(); final SpellAbility sub = sa.getSubAbility();
if (source != null && "ALWAYS".equals(source.getSVar("PlayMain1"))) {
return true;
}
// Cipher spells // Cipher spells
if (sub != null) { if (sub != null) {
final ApiType api = sub.getApi(); final ApiType api = sub.getApi();
@@ -1145,7 +1170,7 @@ public class ComputerUtil {
} }
// get all cards the human controls with AntiBuffedBy // 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) { for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) { if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy"); final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1168,16 +1193,14 @@ public class ComputerUtil {
int activations = sa.getRestrictions().getNumberTurnActivations(); int activations = sa.getRestrictions().getNumberTurnActivations();
if (sa.isTemporary()) { if (sa.isTemporary()) {
final Random r = MyRandom.getRandom(); return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
return r.nextFloat() >= .95; // Abilities created by static abilities have no memory
} }
if (activations < 10) { //10 activations per turn should still be acceptable if (activations < 10) { //10 activations per turn should still be acceptable
return false; return false;
} }
final Random r = MyRandom.getRandom(); return MyRandom.getRandom().nextFloat() >= Math.pow(.95, activations);
return r.nextFloat() >= Math.pow(.95, activations);
} }
public static boolean activateForCost(SpellAbility sa, final Player ai) { public static boolean activateForCost(SpellAbility sa, final Player ai) {
@@ -1220,9 +1243,9 @@ public class ComputerUtil {
return false; return false;
} }
public static boolean hasACardGivingHaste(final Player ai) { public static boolean hasACardGivingHaste(final Player ai, final boolean checkOpponentCards) {
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield)); final CardCollection all = new CardCollection(ai.getCardsIn(Lists.newArrayList(ZoneType.Battlefield, ZoneType.Command)));
// Special for Anger // Special for Anger
if (!ai.getGame().isCardInPlay("Yixlid Jailer") if (!ai.getGame().isCardInPlay("Yixlid Jailer")
&& !ai.getCardsIn(ZoneType.Graveyard, "Anger").isEmpty() && !ai.getCardsIn(ZoneType.Graveyard, "Anger").isEmpty()
@@ -1232,7 +1255,7 @@ public class ComputerUtil {
// Special for Odric // Special for Odric
if (ai.isCardInPlay("Odric, Lunarch Marshal") if (ai.isCardInPlay("Odric, Lunarch Marshal")
&& !CardLists.getKeyword(all, "Haste").isEmpty()) { && !CardLists.getKeyword(all, Keyword.HASTE).isEmpty()) {
return true; return true;
} }
@@ -1288,6 +1311,28 @@ public class ComputerUtil {
} }
} }
} }
if (checkOpponentCards) {
// Check if the opponents have any cards giving Haste to all creatures on the battlefield
CardCollection opp = new CardCollection();
opp.addAll(ai.getOpponents().getCardsIn(ZoneType.Battlefield));
opp.addAll(ai.getOpponents().getCardsIn(ZoneType.Command));
for (final Card c : opp) {
for (StaticAbility stAb : c.getStaticAbilities()) {
Map<String, String> params = stAb.getMapParams();
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
&& params.get("AddKeyword").contains("Haste")) {
final ArrayList<String> affected = Lists.newArrayList(params.get("Affected").split(","));
if (affected.contains("Creature")) {
return true;
}
}
}
}
}
return false; return false;
} }
@@ -1315,7 +1360,7 @@ public class ComputerUtil {
int damage = 0; int damage = 0;
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield)); final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.addAll(ai.getCardsActivableInExternalZones(true)); all.addAll(ai.getCardsActivableInExternalZones(true));
all.addAll(ai.getCardsIn(ZoneType.Hand)); all.addAll(CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(Presets.PERMANENTS)));
for (final Card c : all) { for (final Card c : all) {
for (final SpellAbility sa : c.getSpellAbilities()) { for (final SpellAbility sa : c.getSpellAbilities()) {
@@ -1331,7 +1376,7 @@ public class ComputerUtil {
if (tgt == null) { if (tgt == null) {
continue; continue;
} }
final Player enemy = ai.getOpponent(); final Player enemy = ComputerUtil.getOpponentFor(ai);
if (!sa.canTarget(enemy)) { if (!sa.canTarget(enemy)) {
continue; continue;
} }
@@ -1340,7 +1385,26 @@ public class ComputerUtil {
} }
damage = dmg; damage = dmg;
} }
// Triggered abilities
if (c.isCreature() && c.isInZone(ZoneType.Battlefield) && CombatUtil.canAttack(c)) {
for (final Trigger t : c.getTriggers()) {
if ("Attacks".equals(t.getParam("Mode")) && t.hasParam("Execute")) {
String exec = c.getSVar(t.getParam("Execute"));
if (!exec.isEmpty()) {
SpellAbility trigSa = AbilityFactory.getAbility(exec, c);
if (trigSa != null && trigSa.getApi() == ApiType.LoseLife
&& trigSa.getParamOrDefault("Defined", "").contains("Opponent")) {
trigSa.setHostCard(c);
damage += AbilityUtils.calculateAmount(trigSa.getHostCard(), trigSa.getParam("LifeAmount"), trigSa);
}
}
}
}
}
} }
return damage; return damage;
} }
@@ -1435,20 +1499,29 @@ public class ComputerUtil {
} }
objects = canBeTargeted; objects = canBeTargeted;
} }
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) { SpellAbility saviorWithSubs = saviour;
toughness = saviour.hasParam("NumDef") ? ApiType saviorWithSubsApi = saviorWithSubs == null ? null : saviorWithSubs.getApi();
AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("NumDef"), saviour) : 0; while (saviorWithSubs != null) {
final List<String> keywords = saviour.hasParam("KW") ? ApiType curApi = saviorWithSubs.getApi();
Arrays.asList(saviour.getParam("KW").split(" & ")) : new ArrayList<String>(); if (curApi == ApiType.Pump || curApi == ApiType.PumpAll) {
if (keywords.contains("Indestructible")) { toughness = saviorWithSubs.hasParam("NumDef") ?
grantIndestructible = true; AbilityUtils.calculateAmount(saviorWithSubs.getHostCard(), saviorWithSubs.getParam("NumDef"), saviour) : 0;
} final List<String> keywords = saviorWithSubs.hasParam("KW") ?
if (keywords.contains("Hexproof") || keywords.contains("Shroud")) { Arrays.asList(saviorWithSubs.getParam("KW").split(" & ")) : new ArrayList<String>();
grantShroud = true; if (keywords.contains("Indestructible")) {
grantIndestructible = true;
}
if (keywords.contains("Hexproof") || keywords.contains("Shroud")) {
grantShroud = true;
}
break;
} }
// Consider pump in subabilities, e.g. Bristling Hydra hexproof subability
saviorWithSubs = saviorWithSubs.getSubAbility();
} }
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) { if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
if (saviour.getParam("CounterType").equals("P1P1")) { if (saviour.getParam("CounterType").equals("P1P1")) {
toughness = AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("CounterNum"), saviour); toughness = AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("CounterNum"), saviour);
@@ -1481,7 +1554,7 @@ public class ComputerUtil {
final Card c = (Card) o; final Card c = (Card) o;
// indestructible // indestructible
if (c.hasKeyword("Indestructible")) { if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
continue; continue;
} }
@@ -1528,7 +1601,7 @@ public class ComputerUtil {
} else if (o instanceof Player) { } else if (o instanceof Player) {
final Player p = (Player) o; final Player p = (Player) o;
if (source.hasKeyword("Infect")) { if (source.hasKeyword(Keyword.INFECT)) {
if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) { if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) {
threatened.add(p); threatened.add(p);
} }
@@ -1549,14 +1622,14 @@ public class ComputerUtil {
if (o instanceof Card) { if (o instanceof Card) {
final Card c = (Card) o; final Card c = (Card) o;
final boolean canRemove = (c.getNetToughness() <= dmg) final boolean canRemove = (c.getNetToughness() <= dmg)
|| (!c.hasKeyword("Indestructible") && c.getShieldCount() == 0 && (dmg >= ComputerUtilCombat.getDamageToKill(c))); || (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && (dmg >= ComputerUtilCombat.getDamageToKill(c)));
if (!canRemove) { if (!canRemove) {
continue; continue;
} }
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) { if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
final boolean cantSave = c.getNetToughness() + toughness <= dmg final boolean cantSave = c.getNetToughness() + toughness <= dmg
|| (!c.hasKeyword("Indestructible") && c.getShieldCount() == 0 && !grantIndestructible || (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && !grantIndestructible
&& (dmg >= toughness + ComputerUtilCombat.getDamageToKill(c))); && (dmg >= toughness + ComputerUtilCombat.getDamageToKill(c)));
if (cantSave && (tgt == null || !grantShroud)) { if (cantSave && (tgt == null || !grantShroud)) {
continue; continue;
@@ -1588,14 +1661,15 @@ public class ComputerUtil {
// Destroy => regeneration/bounce/shroud // Destroy => regeneration/bounce/shroud
else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll) else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll)
&& (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) && (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll)
&& !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone && !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone
|| saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll || saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|| saviourApi == ApiType.Protection || saviourApi == null)) { || saviourApi == ApiType.Protection || saviourApi == null
|| saviorWithSubsApi == ApiType.Pump || saviorWithSubsApi == ApiType.PumpAll)) {
for (final Object o : objects) { for (final Object o : objects) {
if (o instanceof Card) { if (o instanceof Card) {
final Card c = (Card) o; final Card c = (Card) o;
// indestructible // indestructible
if (c.hasKeyword("Indestructible")) { if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
continue; continue;
} }
@@ -1604,7 +1678,9 @@ public class ComputerUtil {
continue; continue;
} }
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) { if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|| saviorWithSubsApi == ApiType.Pump
|| saviorWithSubsApi == ApiType.PumpAll) {
if ((tgt == null && !grantIndestructible) if ((tgt == null && !grantIndestructible)
|| (!grantShroud && !grantIndestructible)) { || (!grantShroud && !grantIndestructible)) {
continue; continue;
@@ -1857,6 +1933,27 @@ public class ComputerUtil {
public static boolean scryWillMoveCardToBottomOfLibrary(Player player, Card c) { public static boolean scryWillMoveCardToBottomOfLibrary(Player player, Card c) {
boolean bottom = false; boolean bottom = false;
// AI profile-based toggles
int maxLandsToScryLandsToTop = 3;
int minLandsToScryLandsAway = 8;
int minCreatsToScryCreatsAway = 5;
int minCreatEvalThreshold = 160; // just a bit higher than a baseline 2/2 creature or a 1/1 mana dork
int lowCMCThreshold = 3;
int maxCreatsToScryLowCMCAway = 3;
boolean uncastablesToBottom = false;
int uncastableCMCThreshold = 1;
if (player.getController().isAI()) {
AiController aic = ((PlayerControllerAi)player.getController()).getAi();
maxLandsToScryLandsToTop = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_STILL_NEED_MORE);
minLandsToScryLandsAway = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE);
minCreatsToScryCreatsAway = aic.getIntProperty(AiProps.SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES);
minCreatEvalThreshold = aic.getIntProperty(AiProps.SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE);
lowCMCThreshold = aic.getIntProperty(AiProps.SCRY_EVALTHR_CMC_THRESHOLD);
maxCreatsToScryLowCMCAway = aic.getIntProperty(AiProps.SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC);
uncastablesToBottom = aic.getBooleanProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM);
uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
}
CardCollectionView allCards = player.getAllCards(); CardCollectionView allCards = player.getAllCards();
CardCollectionView cardsInHand = player.getCardsIn(ZoneType.Hand); CardCollectionView cardsInHand = player.getCardsIn(ZoneType.Hand);
CardCollectionView cardsOTB = player.getCardsIn(ZoneType.Battlefield); CardCollectionView cardsOTB = player.getCardsIn(ZoneType.Battlefield);
@@ -1871,8 +1968,9 @@ public class ComputerUtil {
CardCollectionView allCreatures = CardLists.filter(allCards, Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isOwner(player))); CardCollectionView allCreatures = CardLists.filter(allCards, Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isOwner(player)));
int numCards = allCreatures.size(); int numCards = allCreatures.size();
if (landsOTB.size() < 3 && landsInHand.isEmpty()) { if (landsOTB.size() < maxLandsToScryLandsToTop && landsInHand.isEmpty()) {
if ((!c.isLand() && !manaArts.contains(c.getName())) || c.getManaAbilities().isEmpty()) { if ((!c.isLand() && !manaArts.contains(c.getName()))
|| (c.getManaAbilities().isEmpty() && !c.hasABasicLandType())) {
// scry away non-lands and non-manaproducing lands in situations when the land count // scry away non-lands and non-manaproducing lands in situations when the land count
// on the battlefield is low, to try to improve the mana base early // on the battlefield is low, to try to improve the mana base early
bottom = true; bottom = true;
@@ -1880,7 +1978,7 @@ public class ComputerUtil {
} }
if (c.isLand()) { if (c.isLand()) {
if (landsOTB.size() >= 8) { if (landsOTB.size() >= minLandsToScryLandsAway) {
// probably enough lands not to urgently need another one, so look for more gas instead // probably enough lands not to urgently need another one, so look for more gas instead
bottom = true; bottom = true;
} else if (landsInHand.size() >= Math.max(cardsInHand.size() / 2, 2)) { } else if (landsInHand.size() >= Math.max(cardsInHand.size() / 2, 2)) {
@@ -1898,16 +1996,15 @@ public class ComputerUtil {
} else if (c.isCreature()) { } else if (c.isCreature()) {
CardCollection creaturesOTB = CardLists.filter(cardsOTB, CardPredicates.Presets.CREATURES); CardCollection creaturesOTB = CardLists.filter(cardsOTB, CardPredicates.Presets.CREATURES);
int avgCreatureValue = numCards != 0 ? ComputerUtilCard.evaluateCreatureList(allCreatures) / numCards : 0; int avgCreatureValue = numCards != 0 ? ComputerUtilCard.evaluateCreatureList(allCreatures) / numCards : 0;
int minCreatEvalThreshold = 160; // just a bit higher than a baseline 2/2 creature or a 1/1 mana dork
int maxControlledCMC = Aggregates.max(creaturesOTB, CardPredicates.Accessors.fnGetCmc); int maxControlledCMC = Aggregates.max(creaturesOTB, CardPredicates.Accessors.fnGetCmc);
if (ComputerUtilCard.evaluateCreature(c) < avgCreatureValue) { if (ComputerUtilCard.evaluateCreature(c) < avgCreatureValue) {
if (creaturesOTB.size() > 5) { if (creaturesOTB.size() > minCreatsToScryCreatsAway) {
// if there are more than five creatures and the creature is question is below average for // if there are more than five creatures and the creature is question is below average for
// the deck, scry it to the bottom // the deck, scry it to the bottom
bottom = true; bottom = true;
} else if (creaturesOTB.size() > 3 && c.getCMC() <= 3 } else if (creaturesOTB.size() > maxCreatsToScryLowCMCAway && c.getCMC() <= lowCMCThreshold
&& maxControlledCMC >= 4 && ComputerUtilCard.evaluateCreature(c) <= minCreatEvalThreshold) { && maxControlledCMC >= lowCMCThreshold + 1 && ComputerUtilCard.evaluateCreature(c) <= minCreatEvalThreshold) {
// if we are already at a stage when we have 4+ CMC creatures on the battlefield, // if we are already at a stage when we have 4+ CMC creatures on the battlefield,
// probably worth it to scry away very low value creatures with low CMC // probably worth it to scry away very low value creatures with low CMC
bottom = true; bottom = true;
@@ -1915,6 +2012,15 @@ public class ComputerUtil {
} }
} }
if (uncastablesToBottom && !c.isLand()) {
int cmc = c.isSplitCard() ? Math.min(c.getCMC(Card.SplitCMCMode.LeftSplitCMC), c.getCMC(Card.SplitCMCMode.RightSplitCMC))
: c.getCMC();
int maxCastable = ComputerUtilMana.getAvailableManaEstimate(player, false) + landsInHand.size();
if (cmc - maxCastable >= uncastableCMCThreshold) {
bottom = true;
}
}
return bottom; return bottom;
} }
@@ -2047,7 +2153,7 @@ public class ComputerUtil {
} }
} }
else if (logic.equals("ChosenLandwalk")) { else if (logic.equals("ChosenLandwalk")) {
for (Card c : ai.getOpponent().getLandsInPlay()) { for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
for (String t : c.getType()) { for (String t : c.getType()) {
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) { if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
chosen = t; chosen = t;
@@ -2065,7 +2171,7 @@ public class ComputerUtil {
else if (kindOfType.equals("Land")) { else if (kindOfType.equals("Land")) {
if (logic != null) { if (logic != null) {
if (logic.equals("ChosenLandwalk")) { if (logic.equals("ChosenLandwalk")) {
for (Card c : ai.getOpponent().getLandsInPlay()) { for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
for (String t : c.getType().getLandTypes()) { for (String t : c.getType().getLandTypes()) {
if (!invalidTypes.contains(t)) { if (!invalidTypes.contains(t)) {
chosen = t; chosen = t;
@@ -2098,7 +2204,7 @@ public class ComputerUtil {
case "Torture": case "Torture":
return "Torture"; return "Torture";
case "GraceOrCondemnation": case "GraceOrCondemnation":
return ai.getCreaturesInPlay().size() > ai.getOpponent().getCreaturesInPlay().size() ? "Grace" return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
: "Condemnation"; : "Condemnation";
case "CarnageOrHomage": case "CarnageOrHomage":
CardCollection cardsInPlay = CardLists CardCollection cardsInPlay = CardLists
@@ -2529,8 +2635,8 @@ public class ComputerUtil {
// and also on Chronozoa // and also on Chronozoa
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName()))) || (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|| type == CounterType.GOLD || type == CounterType.MUSIC || type == CounterType.PUPA || type == CounterType.GOLD || type == CounterType.MUSIC || type == CounterType.PUPA
|| type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP || type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP
|| type == CounterType.SLEIGHT || type == CounterType.WAGE; || type == CounterType.SLUMBER || type == CounterType.SLEIGHT || type == CounterType.WAGE;
} }
// this countertypes has no effect // this countertypes has no effect
@@ -2683,4 +2789,124 @@ public class ComputerUtil {
} }
return true; return true;
} }
@Deprecated
public static final Player getOpponentFor(final Player player) {
// This method is deprecated and currently functions as a synonym for player.getWeakestOpponent
// until it can be replaced everywhere in the code.
// Consider replacing calls to this method either with a multiplayer-friendly determination of
// opponent that contextually makes the most sense, or with a direct call to player.getWeakestOpponent
// where that is applicable and makes sense from the point of view of multiplayer AI logic.
Player opponent = player.getWeakestOpponent();
if (opponent != null) {
return opponent;
}
throw new IllegalStateException("No opponents left ingame for " + player);
}
public static int countUsefulCreatures(Player p) {
CardCollection creats = p.getCreaturesInPlay();
int count = 0;
for (Card c : creats) {
if (!ComputerUtilCard.isUselessCreature(p, c)) {
count ++;
}
}
return count;
}
public static boolean isPlayingReanimator(final Player ai) {
// TODO: either add SVars to other reanimator cards, or improve the prediction so that it avoids using a SVar
// at all but detects this effect from SA parameters (preferred, but difficult)
CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
CardCollectionView inDeck = ai.getCardsIn(new ZoneType[] {ZoneType.Hand, ZoneType.Library});
Predicate<Card> markedAsReanimator = new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return "true".equalsIgnoreCase(card.getSVar("IsReanimatorCard"));
}
};
int numInHand = CardLists.filter(inHand, markedAsReanimator).size();
int numInDeck = CardLists.filter(inDeck, markedAsReanimator).size();
return numInHand > 0 || numInDeck >= 3;
}
public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) {
final Card source = sa.getHostCard();
if (source == null) { return srcList; }
if (sa.hasParam("AITgts")) {
CardCollection list;
if (sa.getParam("AITgts").equals("BetterThanSource")) {
int value = ComputerUtilCard.evaluateCreature(source);
if (source.isEnchanted()) {
for (Card enc : source.getEnchantedBy(false)) {
if (enc.getController().equals(ai)) {
value += 100; // is 100 per AI's own aura enough?
}
}
}
final int totalValue = value;
list = CardLists.filter(srcList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return ComputerUtilCard.evaluateCreature(c) > totalValue + 30;
}
});
} else {
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
}
if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) {
return list;
} else {
return srcList;
}
}
return srcList;
}
// Check if AI life is in danger/serious danger based on next expected combat
// assuming a loss of "payment" life
// call this to determine if it's safe to use a life payment spell
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
Player opponent = ComputerUtil.getOpponentFor(ai);
// test whether the human can kill the ai next turn
Combat combat = new Combat(opponent);
boolean containsAttacker = false;
for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai);
containsAttacker = true;
}
}
if (!containsAttacker) {
return false;
}
AiBlockController block = new AiBlockController(ai);
block.assignBlockersForCombat(combat);
// TODO predict other, noncombat sources of damage and add them to the "payment" variable.
// examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc
// If added, might need a parameter to define whether we want to check all threats or combat threats.
if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
return true;
}
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
return true;
}
return false;
}
} }

View File

@@ -1,137 +1,174 @@
package forge.ai; package forge.ai;
import java.util.ArrayList; import java.util.Iterator;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.card.CardStateName;
import forge.game.Game; import forge.card.CardStateName;
import forge.game.GameActionUtil; import forge.game.Game;
import forge.game.card.Card; import forge.game.GameActionUtil;
import forge.game.card.CardCollection; import forge.game.ability.ApiType;
import forge.game.card.CardCollectionView; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardCollection;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardCollectionView;
import forge.game.player.Player; import forge.game.card.CardLists;
import forge.game.spellability.SpellAbility; import forge.game.card.CardPredicates.Presets;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.spellability.SpellAbility;
import java.util.Iterator; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.ZoneType;
public class ComputerUtilAbility {
public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) { public class ComputerUtilAbility {
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) { public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) {
return null; if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
} return null;
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand)); }
hand.addAll(player.getCardsIn(ZoneType.Exile)); final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
CardCollection landList = CardLists.filter(hand, Presets.LANDS); hand.addAll(player.getCardsIn(ZoneType.Exile));
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
//filter out cards that can't be played
landList = CardLists.filter(landList, new Predicate<Card>() { //filter out cards that can't be played
@Override landList = CardLists.filter(landList, new Predicate<Card>() {
public boolean apply(final Card c) { @Override
if (!c.getSVar("NeedsToPlay").isEmpty()) { public boolean apply(final Card c) {
final String needsToPlay = c.getSVar("NeedsToPlay"); if (!c.getSVar("NeedsToPlay").isEmpty()) {
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null); final String needsToPlay = c.getSVar("NeedsToPlay");
if (list.isEmpty()) { CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null);
return false; if (list.isEmpty()) {
} return false;
} }
return player.canPlayLand(c); }
} return player.canPlayLand(c);
}); }
});
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile)); final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
if (!player.getCardsIn(ZoneType.Library).isEmpty()) { landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile));
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0)); if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
} landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
for (final Card crd : landsNotInHand) { }
if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) { for (final Card crd : landsNotInHand) {
continue; if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
} continue;
if (!crd.mayPlay(player).isEmpty()) { }
landList.add(crd); if (!crd.mayPlay(player).isEmpty()) {
} landList.add(crd);
} }
if (landList.isEmpty()) { }
return null; if (landList.isEmpty()) {
} return null;
return landList; }
} return landList;
}
public static CardCollection getAvailableCards(final Game game, final Player player) {
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand)); public static CardCollection getAvailableCards(final Game game, final Player player) {
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
all.addAll(player.getCardsIn(ZoneType.Graveyard));
all.addAll(player.getCardsIn(ZoneType.Command)); all.addAll(player.getCardsIn(ZoneType.Graveyard));
if (!player.getCardsIn(ZoneType.Library).isEmpty()) { all.addAll(player.getCardsIn(ZoneType.Command));
all.add(player.getCardsIn(ZoneType.Library).get(0)); if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
} all.add(player.getCardsIn(ZoneType.Library).get(0));
for(Player p : game.getPlayers()) { }
all.addAll(p.getCardsIn(ZoneType.Exile)); for(Player p : game.getPlayers()) {
all.addAll(p.getCardsIn(ZoneType.Battlefield)); all.addAll(p.getCardsIn(ZoneType.Exile));
} all.addAll(p.getCardsIn(ZoneType.Battlefield));
return all; }
} return all;
}
public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
final List<SpellAbility> spellAbilities = new ArrayList<SpellAbility>(); public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
for (final Card c : l) { final List<SpellAbility> spellAbilities = Lists.newArrayList();
for (final SpellAbility sa : c.getSpellAbilities()) { for (final Card c : l) {
spellAbilities.add(sa); for (final SpellAbility sa : c.getSpellAbilities()) {
} spellAbilities.add(sa);
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) { }
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) { if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
spellAbilities.add(sa); for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
} spellAbilities.add(sa);
} }
} }
return spellAbilities; }
} return spellAbilities;
}
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
final List<SpellAbility> newAbilities = new ArrayList<SpellAbility>(); public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
for (SpellAbility sa : originList) { final List<SpellAbility> newAbilities = Lists.newArrayList();
sa.setActivatingPlayer(player); for (SpellAbility sa : originList) {
//add alternative costs as additional spell abilities sa.setActivatingPlayer(player);
newAbilities.add(sa); //add alternative costs as additional spell abilities
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player)); newAbilities.add(sa);
} newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
final List<SpellAbility> result = new ArrayList<SpellAbility>();
for (SpellAbility sa : newAbilities) { final List<SpellAbility> result = Lists.newArrayList();
sa.setActivatingPlayer(player); for (SpellAbility sa : newAbilities) {
result.addAll(GameActionUtil.getOptionalCosts(sa)); sa.setActivatingPlayer(player);
} result.addAll(GameActionUtil.getOptionalCosts(sa));
return result; }
} return result;
}
public static SpellAbility getTopSpellAbilityOnStack(Game game, SpellAbility sa) {
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator(); public static SpellAbility getTopSpellAbilityOnStack(Game game, SpellAbility sa) {
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
if (!it.hasNext()) {
return null; if (!it.hasNext()) {
} return null;
}
SpellAbility tgtSA = it.next().getSpellAbility(true);
// Grab the topmost spellability that isn't this SA and use that for comparisons SpellAbility tgtSA = it.next().getSpellAbility(true);
if (sa.equals(tgtSA) && game.getStack().size() > 1) { // Grab the topmost spellability that isn't this SA and use that for comparisons
if (!it.hasNext()) { if (sa.equals(tgtSA) && game.getStack().size() > 1) {
return null; if (!it.hasNext()) {
} return null;
tgtSA = it.next().getSpellAbility(true); }
} tgtSA = it.next().getSpellAbility(true);
return tgtSA; }
} return tgtSA;
}
public static Card getAbilitySource(SpellAbility sa) {
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard(); public static Card getAbilitySource(SpellAbility sa) {
} return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
}
public static String getAbilitySourceName(SpellAbility sa) {
return sa.getOriginalHost() != null ? sa.getOriginalHost().getName() : sa.getHostCard() != null ? sa.getHostCard().getName() : ""; public static String getAbilitySourceName(SpellAbility sa) {
} final Card c = getAbilitySource(sa);
} return c != null ? c.getName() : "";
}
public static CardCollection getCardsTargetedWithApi(Player ai, CardCollection cardList, SpellAbility sa, ApiType api) {
// Returns a collection of cards which have already been targeted with the given API either in the parent ability,
// in the sub ability, or by something on stack. If "sa" is specified, the parent and sub abilities of this SA will
// be checked for targets. If "sa" is null, only the stack instances will be checked.
CardCollection targeted = new CardCollection();
if (sa != null) {
SpellAbility saSub = sa.getRootAbility();
while (saSub != null) {
if (saSub.getApi() == api && saSub.getTargets() != null) {
for (Card c : cardList) {
if (saSub.getTargets().getTargetCards().contains(c)) {
// Was already targeted with this API in a parent or sub SA
targeted.add(c);
}
}
}
saSub = saSub.getSubAbility();
}
}
for (SpellAbilityStackInstance si : ai.getGame().getStack()) {
SpellAbility ab = si.getSpellAbility(false);
if (ab != null && ab.getApi() == api && si.getTargetChoices() != null) {
for (Card c : cardList) {
// TODO: somehow ensure that the detected SA won't be countered
if (si.getTargetChoices().getTargetCards().contains(c)) {
// Was already targeted by a spell ability instance on stack
targeted.add(c);
}
}
}
}
return targeted;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,215 +1,271 @@
package forge.ai; package forge.ai;
import com.google.common.base.Function; import com.google.common.base.Function;
import forge.game.card.Card; import forge.game.ability.AbilityUtils;
import forge.game.spellability.SpellAbility; import forge.game.ability.ApiType;
import forge.game.card.Card;
public class CreatureEvaluator implements Function<Card, Integer> { import forge.game.card.CounterType;
protected int getEffectivePower(final Card c) { import forge.game.cost.CostPayEnergy;
return c.getNetCombatDamage(); import forge.game.keyword.Keyword;
} import forge.game.keyword.KeywordInterface;
protected int getEffectiveToughness(final Card c) { import forge.game.spellability.SpellAbility;
return c.getNetToughness();
} public class CreatureEvaluator implements Function<Card, Integer> {
protected int getEffectivePower(final Card c) {
@Override return c.getNetCombatDamage();
public Integer apply(Card c) { }
return evaluateCreature(c); protected int getEffectiveToughness(final Card c) {
} return c.getNetToughness();
}
public int evaluateCreature(final Card c) {
int value = 80; @Override
if (!c.isToken()) { public Integer apply(Card c) {
value += addValue(20, "non-token"); // tokens should be worth less than actual cards return evaluateCreature(c);
} }
int power = getEffectivePower(c);
final int toughness = getEffectiveToughness(c); public int evaluateCreature(final Card c) {
for (String keyword : c.getKeywords()) { return evaluateCreature(c, true, true);
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.") }
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.") public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) {
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) { int value = 80;
power = 0; if (!c.isToken()) {
break; value += addValue(20, "non-token"); // tokens should be worth less than actual cards
} }
} int power = getEffectivePower(c);
value += addValue(power * 15, "power"); final int toughness = getEffectiveToughness(c);
value += addValue(toughness * 10, "toughness: " + toughness); for (KeywordInterface kw : c.getKeywords()) {
value += addValue(c.getCMC() * 5, "cmc"); String keyword = kw.getOriginal();
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
// Evasion keywords || keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
if (c.hasKeyword("Flying")) { || keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
value += addValue(power * 10, "flying"); || keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
} power = 0;
if (c.hasKeyword("Horsemanship")) { break;
value += addValue(power * 10, "horses"); }
} }
if (c.hasKeyword("Unblockable")) { if (considerPT) {
value += addValue(power * 10, "unblockable"); value += addValue(power * 15, "power");
} else { value += addValue(toughness * 10, "toughness: " + toughness);
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { }
value += addValue(power * 6, "thorns"); if (considerCMC) {
} value += addValue(c.getCMC() * 5, "cmc");
if (c.hasKeyword("Fear")) { }
value += addValue(power * 6, "fear");
} // Evasion keywords
if (c.hasKeyword("Intimidate")) { if (c.hasKeyword(Keyword.FLYING)) {
value += addValue(power * 6, "intimidate"); value += addValue(power * 10, "flying");
} }
if (c.hasStartOfKeyword("Menace")) { if (c.hasKeyword(Keyword.HORSEMANSHIP)) {
value += addValue(power * 4, "menace"); value += addValue(power * 10, "horses");
} }
if (c.hasStartOfKeyword("CantBeBlockedBy")) { if (c.hasKeyword("Unblockable")) {
value += addValue(power * 3, "block-restrict"); value += addValue(power * 10, "unblockable");
} } else {
} if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
value += addValue(power * 6, "thorns");
// Other good keywords }
if (power > 0) { if (c.hasKeyword(Keyword.FEAR)) {
if (c.hasKeyword("Double Strike")) { value += addValue(power * 6, "fear");
value += addValue(10 + (power * 15), "ds"); }
} else if (c.hasKeyword("First Strike")) { if (c.hasKeyword(Keyword.INTIMIDATE)) {
value += addValue(10 + (power * 5), "fs"); value += addValue(power * 6, "intimidate");
} }
if (c.hasKeyword("Deathtouch")) { if (c.hasKeyword(Keyword.MENACE)) {
value += addValue(25, "dt"); value += addValue(power * 4, "menace");
} }
if (c.hasKeyword("Lifelink")) { if (c.hasStartOfKeyword("CantBeBlockedBy")) {
value += addValue(power * 10, "lifelink"); value += addValue(power * 3, "block-restrict");
} }
if (power > 1 && c.hasKeyword("Trample")) { }
value += addValue((power - 1) * 5, "trample");
} // Other good keywords
if (c.hasKeyword("Vigilance")) { if (power > 0) {
value += addValue((power * 5) + (toughness * 5), "vigilance"); if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
} value += addValue(10 + (power * 15), "ds");
if (c.hasKeyword("Wither")) { } else if (c.hasKeyword(Keyword.FIRST_STRIKE)) {
value += addValue(power * 10, "Wither"); value += addValue(10 + (power * 5), "fs");
} }
if (c.hasKeyword("Infect")) { if (c.hasKeyword(Keyword.DEATHTOUCH)) {
value += addValue(power * 15, "infect"); value += addValue(25, "dt");
} }
value += addValue(c.getKeywordMagnitude("Rampage"), "rampage"); if (c.hasKeyword(Keyword.LIFELINK)) {
} value += addValue(power * 10, "lifelink");
}
value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido"); if (power > 1 && c.hasKeyword(Keyword.TRAMPLE)) {
value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking"); value += addValue((power - 1) * 5, "trample");
value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted"); }
value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi"); if (c.hasKeyword(Keyword.VIGILANCE)) {
value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb"); value += addValue((power * 5) + (toughness * 5), "vigilance");
}
// Defensive Keywords if (c.hasKeyword(Keyword.WITHER)) {
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) { value += addValue(power * 10, "Wither");
value += addValue(5, "reach"); }
} if (c.hasKeyword(Keyword.INFECT)) {
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) { value += addValue(power * 15, "infect");
value += addValue(3, "shadow-block"); }
} value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage");
value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict");
// Protection }
if (c.hasKeyword("Indestructible")) {
value += addValue(70, "darksteel"); value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido");
} value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking");
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) { value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted");
value += addValue(60, "cho-manno"); value += addValue(c.getKeywordMagnitude(Keyword.ANNIHILATOR) * 50, "eldrazi");
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) { value += addValue(c.getKeywordMagnitude(Keyword.ABSORB) * 11, "absorb");
value += addValue(50, "fogbank");
} // Keywords that may produce temporary or permanent buffs over time
if (c.hasKeyword("Hexproof")) { if (c.hasKeyword(Keyword.PROWESS)) {
value += addValue(35, "hexproof"); value += addValue(5, "prowess");
} else if (c.hasKeyword("Shroud")) { }
value += addValue(30, "shroud"); if (c.hasKeyword(Keyword.OUTLAST)) {
} value += addValue(10, "outlast");
if (c.hasStartOfKeyword("Protection")) { }
value += addValue(20, "protection");
} // Defensive Keywords
if (c.hasStartOfKeyword("PreventAllDamageBy")) { if (c.hasKeyword(Keyword.REACH) && !c.hasKeyword(Keyword.FLYING)) {
value += addValue(10, "prevent-dmg"); value += addValue(5, "reach");
} }
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
// Bad keywords value += addValue(3, "shadow-block");
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) { }
value -= subValue((power * 9) + 40, "defender");
} else if (c.getSVar("SacrificeEndCombat").equals("True")) { // Protection
value -= subValue(40, "sac-end"); if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
} value += addValue(70, "darksteel");
if (c.hasKeyword("CARDNAME can't block.")) { }
value -= subValue(10, "cant-block"); if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
} else if (c.hasKeyword("CARDNAME attacks each turn if able.") value += addValue(60, "cho-manno");
|| c.hasKeyword("CARDNAME attacks each combat if able.")) { } else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
value -= subValue(10, "must-attack"); value += addValue(50, "fogbank");
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) { }
value -= subValue(10, "must-attack-player"); if (c.hasKeyword(Keyword.HEXPROOF)) {
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) { value += addValue(35, "hexproof");
value -= subValue(toughness * 5, "reverse-reach"); } else if (c.hasKeyword(Keyword.SHROUD)) {
} value += addValue(30, "shroud");
}
if (c.hasSVar("DestroyWhenDamaged")) { if (c.hasKeyword(Keyword.PROTECTION)) {
value -= subValue((toughness - 1) * 9, "dies-to-dmg"); value += addValue(20, "protection");
} }
if (c.hasKeyword("CARDNAME can't attack or block.")) { // Bad keywords
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless if (c.hasKeyword(Keyword.DEFENDER) || c.hasKeyword("CARDNAME can't attack.")) {
} value -= subValue((power * 9) + 40, "defender");
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) { } else if (c.getSVar("SacrificeEndCombat").equals("True")) {
if (c.isTapped()) { value -= subValue(40, "sac-end");
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless }
} else { if (c.hasKeyword("CARDNAME can't block.")) {
value -= subValue(50, "doesnt-untap"); value -= subValue(10, "cant-block");
} } else if (c.hasKeyword("CARDNAME attacks each turn if able.")
} || c.hasKeyword("CARDNAME attacks each combat if able.")) {
if (c.hasSVar("EndOfTurnLeavePlay")) { value -= subValue(10, "must-attack");
value -= subValue(50, "eot-leaves"); } else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
} else if (c.hasStartOfKeyword("Cumulative upkeep")) { value -= subValue(10, "must-attack-player");
value -= subValue(30, "cupkeep"); } else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
} else if (c.hasStartOfKeyword("UpkeepCost")) { value -= subValue(toughness * 5, "reverse-reach");
value -= subValue(20, "sac-unless"); }
} else if (c.hasStartOfKeyword("Echo") && c.cameUnderControlSinceLastUpkeep()) {
value -= subValue(10, "echo-unpaid"); if (c.hasSVar("DestroyWhenDamaged")) {
} value -= subValue((toughness - 1) * 9, "dies-to-dmg");
}
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= subValue(20, "upkeep-dmg"); if (c.hasKeyword("CARDNAME can't attack or block.")) {
} value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
if (c.hasStartOfKeyword("Fading")) { }
value -= subValue(20, "fading"); if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
} if (c.isTapped()) {
if (c.hasStartOfKeyword("Vanishing")) { value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
value -= subValue(20, "vanishing"); } else {
} value -= subValue(50, "doesnt-untap");
if (c.getSVar("Targeting").equals("Dies")) { }
value -= subValue(25, "dies"); }
} if (c.hasSVar("EndOfTurnLeavePlay")) {
value -= subValue(50, "eot-leaves");
for (final SpellAbility sa : c.getSpellAbilities()) { } else if (c.hasStartOfKeyword("Cumulative upkeep")) {
if (sa.isAbility()) { value -= subValue(30, "cupkeep");
value += addValue(10, "sa: " + sa); } else if (c.hasStartOfKeyword("UpkeepCost")) {
} value -= subValue(20, "sac-unless");
} } else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) {
if (!c.getManaAbilities().isEmpty()) { value -= subValue(10, "echo-unpaid");
value += addValue(10, "manadork"); }
}
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
if (c.isUntapped()) { value -= subValue(20, "upkeep-dmg");
value += addValue(1, "untapped"); }
} if (c.hasKeyword(Keyword.FADING)) {
value -= subValue(20, "fading");
// paired creatures are more valuable because they grant a bonus to the other creature }
if (c.isPaired()) { if (c.hasKeyword(Keyword.VANISHING)) {
value += addValue(14, "paired"); value -= subValue(20, "vanishing");
} }
if (c.getSVar("Targeting").equals("Dies")) {
if (!c.getEncodedCards().isEmpty()) { value -= subValue(25, "dies");
value += addValue(24, "encoded"); }
}
return value; for (final SpellAbility sa : c.getSpellAbilities()) {
} if (sa.isAbility()) {
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
protected int addValue(int value, String text) { }
return value; }
} if (!c.getManaAbilities().isEmpty()) {
protected int subValue(int value, String text) { value += addValue(10, "manadork");
return -addValue(-value, text); }
}
} if (c.isUntapped()) {
value += addValue(1, "untapped");
}
// paired creatures are more valuable because they grant a bonus to the other creature
if (c.isPaired()) {
value += addValue(14, "paired");
}
if (!c.getEncodedCards().isEmpty()) {
value += addValue(24, "encoded");
}
// card-specific evaluation modifier
if (c.hasSVar("AIEvaluationModifier")) {
int mod = AbilityUtils.calculateAmount(c, c.getSVar("AIEvaluationModifier"), null);
value += mod;
}
return value;
}
private int evaluateSpellAbility(SpellAbility sa) {
// Pump abilities
if (sa.getApi() == ApiType.Pump) {
// Pump abilities that grant +X/+X to the card
if ("+X".equals(sa.getParam("NumAtt"))
&& "+X".equals(sa.getParam("NumDef"))
&& !sa.usesTargeting()
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
// Electrostatic Pummeler, can be expanded for similar cards
int initPower = getEffectivePower(sa.getHostCard());
int pumpedPower = initPower;
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
if (energy > 0) {
int numActivations = energy / 3;
for (int i = 0; i < numActivations; i++) {
pumpedPower *= 2;
}
return (pumpedPower - initPower) * 15;
}
}
}
}
// default value
return 10;
}
protected int addValue(int value, String text) {
return value;
}
protected int subValue(int value, String text) {
return -addValue(-value, text);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +1,71 @@
package forge.ai; package forge.ai;
import java.util.Set; import java.util.Set;
import forge.AIOption; import forge.LobbyPlayer;
import forge.LobbyPlayer; import forge.game.Game;
import forge.game.Game; import forge.game.player.IGameEntitiesFactory;
import forge.game.player.IGameEntitiesFactory; import forge.game.player.Player;
import forge.game.player.Player; import forge.game.player.PlayerController;
import forge.game.player.PlayerController;
public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory {
public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory {
private String aiProfile = "";
private String aiProfile = ""; private boolean rotateProfileEachGame;
private boolean rotateProfileEachGame; private boolean allowCheatShuffle;
private boolean allowCheatShuffle; private boolean useSimulation;
private boolean useSimulation;
public LobbyPlayerAi(String name, Set<AIOption> options) {
public LobbyPlayerAi(String name, Set<AIOption> options) { super(name);
super(name); if (options != null && options.contains(AIOption.USE_SIMULATION)) {
if (options != null && options.contains(AIOption.USE_SIMULATION)) { this.useSimulation = true;
this.useSimulation = true; }
} }
}
public boolean isAllowCheatShuffle() {
public boolean isAllowCheatShuffle() { return allowCheatShuffle;
return allowCheatShuffle; }
}
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
public void setAllowCheatShuffle(boolean allowCheatShuffle) { this.allowCheatShuffle = allowCheatShuffle;
this.allowCheatShuffle = allowCheatShuffle; }
}
public void setAiProfile(String profileName) {
public void setAiProfile(String profileName) { aiProfile = profileName;
aiProfile = profileName; }
}
public String getAiProfile() {
public String getAiProfile() { return aiProfile;
return aiProfile; }
}
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
public void setRotateProfileEachGame(boolean rotateProfileEachGame) { this.rotateProfileEachGame = rotateProfileEachGame;
this.rotateProfileEachGame = rotateProfileEachGame; }
}
private PlayerControllerAi createControllerFor(Player ai) {
private PlayerControllerAi createControllerFor(Player ai) { PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this); result.setUseSimulation(useSimulation);
result.setUseSimulation(useSimulation); result.allowCheatShuffle(allowCheatShuffle);
result.allowCheatShuffle(allowCheatShuffle); return result;
return result; }
}
@Override
@Override public PlayerController createMindSlaveController(Player master, Player slave) {
public PlayerController createMindSlaveController(Player master, Player slave) { return createControllerFor(slave);
return createControllerFor(slave); }
}
@Override
@Override public Player createIngamePlayer(Game game, final int id) {
public Player createIngamePlayer(Game game, final int id) { Player ai = new Player(getName(), game, id);
Player ai = new Player(getName(), game, id); ai.setFirstController(createControllerFor(ai));
ai.setFirstController(createControllerFor(ai));
if (rotateProfileEachGame) {
if (rotateProfileEachGame) { setAiProfile(AiProfileUtil.getRandomProfile());
setAiProfile(AiProfileUtil.getRandomProfile()); /*System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));*/
/*System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));*/ }
} return ai;
return ai; }
}
@Override
@Override public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,360 +1,365 @@
package forge.ai; package forge.ai;
import java.util.Collection; import com.google.common.collect.Iterables;
import java.util.List; import com.google.common.collect.Lists;
import java.util.Map; import forge.card.ICardFace;
import forge.card.mana.ManaCost;
import com.google.common.collect.Iterables; import forge.card.mana.ManaCostParser;
import com.google.common.collect.Lists; import forge.game.GameEntity;
import forge.game.card.Card;
import forge.card.ICardFace; import forge.game.card.CounterType;
import forge.card.mana.ManaCost; import forge.game.cost.Cost;
import forge.card.mana.ManaCostParser; import forge.game.mana.ManaCostBeingPaid;
import forge.game.GameEntity; import forge.game.phase.PhaseHandler;
import forge.game.card.Card; import forge.game.phase.PhaseType;
import forge.game.card.CounterType; import forge.game.player.Player;
import forge.game.cost.Cost; import forge.game.player.PlayerActionConfirmMode;
import forge.game.mana.ManaCostBeingPaid; import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.phase.PhaseHandler; import forge.game.spellability.AbilitySub;
import forge.game.phase.PhaseType; import forge.game.spellability.SpellAbility;
import forge.game.player.Player; import forge.game.spellability.SpellAbilityCondition;
import forge.game.player.PlayerActionConfirmMode; import forge.util.MyRandom;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.AbilitySub; import java.util.Collection;
import forge.game.spellability.SpellAbility; import java.util.List;
import forge.game.spellability.SpellAbilityCondition; import java.util.Map;
import forge.util.MyRandom;
/**
/** * Base class for API-specific AI logic
* Base class for API-specific AI logic * <p>
* <p> * The three main methods are canPlayAI(), chkAIDrawback and doTriggerAINoCost.
* The three main methods are canPlayAI(), chkAIDrawback and doTriggerAINoCost. */
*/ public abstract class SpellAbilityAi {
public abstract class SpellAbilityAi {
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) { if (!canPlayAI(aiPlayer, sa)) {
if (!canPlayAI(aiPlayer, sa)) { return false;
return false; }
} final AbilitySub subAb = sa.getSubAbility();
final AbilitySub subAb = sa.getSubAbility(); return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb); }
}
/**
/** * Handles the AI decision to play a "main" SpellAbility
* Handles the AI decision to play a "main" SpellAbility */
*/ protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
protected boolean canPlayAI(final Player ai, final SpellAbility sa) { final Card source = sa.getHostCard();
final Card source = sa.getHostCard();
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) { return false;
return false; }
}
return canPlayWithoutRestrict(ai, sa);
return canPlayWithoutRestrict(ai, sa); }
}
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) { final Card source = sa.getHostCard();
final Card source = sa.getHostCard(); final Cost cost = sa.getPayCosts();
final Cost cost = sa.getPayCosts();
if (!checkConditions(ai, sa, sa.getConditions())) {
if (!checkConditions(ai, sa, sa.getConditions())) { SpellAbility sub = sa.getSubAbility();
SpellAbility sub = sa.getSubAbility(); if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) { return false;
return false; }
} }
}
if (sa.hasParam("AILogic")) {
if (sa.hasParam("AILogic")) { final String logic = sa.getParam("AILogic");
final String logic = sa.getParam("AILogic"); if (!checkAiLogic(ai, sa, logic)) {
if (!checkAiLogic(ai, sa, logic)) { return false;
return false; }
} if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) { return false;
return false; }
} } else {
} else { if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) { return false;
return false; }
} }
} if (cost != null && !willPayCosts(ai, sa, cost, source)) {
if (cost != null && !willPayCosts(ai, sa, cost, source)) { return false;
return false; }
} return checkApiLogic(ai, sa);
return checkApiLogic(ai, sa); }
}
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) { // copy it to disable some checks that the AI need to check extra
// copy it to disable some checks that the AI need to check extra con = (SpellAbilityCondition) con.copy();
con = (SpellAbilityCondition) con.copy();
// if manaspent, check if AI can pay the colored mana as cost
// if manaspent, check if AI can pay the colored mana as cost if (!con.getManaSpent().isEmpty()) {
if (!con.getManaSpent().isEmpty()) { // need to use ManaCostBeingPaid check, can't use Cost#canPay
// need to use ManaCostBeingPaid check, can't use Cost#canPay ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent())));
ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent()))); if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) {
if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) { con.setManaSpent("");
con.setManaSpent(""); }
} }
}
return con.areMet(sa);
return con.areMet(sa); }
}
/**
/** * Checks if the AI will play a SpellAbility with the specified AiLogic
* Checks if the AI will play a SpellAbility with the specified AiLogic */
*/ protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { if (aiLogic.equals("CheckCondition")) {
return !("Never".equals(aiLogic)); SpellAbility saCopy = sa.copy();
} saCopy.setActivatingPlayer(ai);
return saCopy.getConditions().areMet(saCopy);
/** }
* Checks if the AI is willing to pay for additional costs
* <p> return !("Never".equals(aiLogic));
* Evaluated costs are: life, discard, sacrifice and counter-removal }
*/
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) { /**
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) { * Checks if the AI is willing to pay for additional costs
return false; * <p>
} * Evaluated costs are: life, discard, sacrifice and counter-removal
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) { */
return false; protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
} if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source)) { return false;
return false; }
} if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) { return false;
return false; }
} if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
return true; return false;
} }
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
/** return false;
* Checks if the AI will play a SpellAbility based on its phase restrictions }
*/ return true;
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { }
return true;
} /**
* Checks if the AI will play a SpellAbility based on its phase restrictions
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph, */
final String logic) { protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
return checkPhaseRestrictions(ai, sa, ph); return true;
} }
/**
* The rest of the logic not covered by the canPlayAI template is defined here protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
*/ final String logic) {
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { return checkPhaseRestrictions(ai, sa, ph);
if (ComputerUtil.preventRunAwayActivations(sa)) { }
return false; // prevent infinite loop /**
} * The rest of the logic not covered by the canPlayAI template is defined here
return MyRandom.getRandom().nextFloat() < .8f; // random success */
} protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (ComputerUtil.preventRunAwayActivations(sa)) {
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) { return false; // prevent infinite loop
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) { }
return false; return MyRandom.getRandom().nextFloat() < .8f; // random success
} }
// a mandatory SpellAbility with targeting but without candidates, public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
// does not need to go any deeper if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) { return false;
return false; }
}
// a mandatory SpellAbility with targeting but without candidates,
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory); // does not need to go any deeper
} if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
return false;
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) }
{
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) { return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
return false; }
}
final AbilitySub subAb = sa.getSubAbility(); public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory; {
} if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
return false;
/** }
* Handles the AI decision to play a triggered SpellAbility final AbilitySub subAb = sa.getSubAbility();
*/ return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) { }
if (canPlayWithoutRestrict(aiPlayer, sa)) {
return true; /**
} * Handles the AI decision to play a triggered SpellAbility
*/
// not mandatory, short way out protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (!mandatory) { if (canPlayWithoutRestrict(aiPlayer, sa)) {
return false; return true;
} }
// invalid target might prevent it // not mandatory, short way out
if (sa.usesTargeting()) { if (!mandatory) {
// make list of players it does try to target return false;
List<Player> players = Lists.newArrayList(); }
players.addAll(aiPlayer.getOpponents());
players.addAll(aiPlayer.getAllies()); // invalid target might prevent it
players.add(aiPlayer); if (sa.usesTargeting()) {
// make list of players it does try to target
// try to target opponent, then ally, then itself List<Player> players = Lists.newArrayList();
for (final Player p : players) { players.addAll(aiPlayer.getOpponents());
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) { players.addAll(aiPlayer.getAllies());
sa.resetTargets(); players.add(aiPlayer);
sa.getTargets().add(p);
return true; // try to target opponent, then ally, then itself
} for (final Player p : players) {
} if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
sa.resetTargets();
return false; sa.getTargets().add(p);
} return true;
return true; }
} }
/** return false;
* Handles the AI decision to play a sub-SpellAbility }
*/ return true;
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) { }
// sub-SpellAbility might use targets too
if (sa.usesTargeting()) { /**
// no Candidates, no adding to Stack * Handles the AI decision to play a sub-SpellAbility
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) { */
return false; public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
} // sub-SpellAbility might use targets too
// but if it does, it should override this function if (sa.usesTargeting()) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chkAIDrawback is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method"); // no Candidates, no adding to Stack
return false; if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
} return false;
return true; }
} // but if it does, it should override this function
System.err.println("Warning: default (ie. inherited from base class) implementation of chkAIDrawback is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
/** return false;
* <p> }
* isSorcerySpeed. return true;
* </p> }
*
* @param sa /**
* a {@link forge.game.spellability.SpellAbility} object. * <p>
* @return a boolean. * isSorcerySpeed.
*/ * </p>
protected static boolean isSorcerySpeed(final SpellAbility sa) { *
return (sa.isSpell() && sa.getHostCard().isSorcery()) * @param sa
|| (sa.isAbility() && sa.getRestrictions().isSorcerySpeed()) * a {@link forge.game.spellability.SpellAbility} object.
|| (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed.")); * @return a boolean.
} */
protected static boolean isSorcerySpeed(final SpellAbility sa) {
/** return (sa.isSpell() && sa.getHostCard().isSorcery())
* <p> || (sa.isAbility() && sa.getRestrictions().isSorcerySpeed())
* playReusable. || (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
* </p> }
*
* @param sa /**
* a {@link forge.game.spellability.SpellAbility} object. * <p>
* @return a boolean. * playReusable.
*/ * </p>
protected static boolean playReusable(final Player ai, final SpellAbility sa) { *
PhaseHandler phase = ai.getGame().getPhaseHandler(); * @param sa
* a {@link forge.game.spellability.SpellAbility} object.
// TODO probably also consider if winter orb or similar are out * @return a boolean.
*/
if (sa.getPayCosts() == null || sa instanceof AbilitySub) { protected static boolean playReusable(final Player ai, final SpellAbility sa) {
return true; // This is only true for Drawbacks and triggers PhaseHandler phase = ai.getGame().getPhaseHandler();
}
// TODO probably also consider if winter orb or similar are out
if (!sa.getPayCosts().isReusuableResource()) {
return false; if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
} return true; // This is only true for Drawbacks and triggers
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true; if (!sa.getPayCosts().isReusuableResource()) {
} return false;
}
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
return true; if (ComputerUtil.playImmediately(ai, sa)) {
} return true;
if (sa.isSpell() && !sa.isBuyBackAbility()) { }
return false;
} if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
return true;
}
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai); if (sa.isSpell() && !sa.isBuyBackAbility()) {
} return false;
}
/**
* TODO: Write javadoc for this method.
* @param aiPlayer return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
* @param ab }
* @return
*/ /**
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) { * TODO: Write javadoc for this method.
final AbilitySub subAb = ab.getSubAbility(); * @param aiPlayer
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb)); * @param ab
} * @return
*/
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method"); final AbilitySub subAb = ab.getSubAbility();
return true; return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
} }
@SuppressWarnings("unchecked") public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) { System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
boolean hasPlayer = false; return true;
boolean hasCard = false; }
boolean hasPlaneswalker = false;
@SuppressWarnings("unchecked")
for (T ent : options) { public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
if (ent instanceof Player) { boolean hasPlayer = false;
hasPlayer = true; boolean hasCard = false;
} else if (ent instanceof Card) { boolean hasPlaneswalker = false;
hasCard = true;
if (((Card)ent).isPlaneswalker()) { for (T ent : options) {
hasPlaneswalker = true; if (ent instanceof Player) {
} hasPlayer = true;
} } else if (ent instanceof Card) {
} hasCard = true;
if (((Card)ent).isPlaneswalker()) {
if (hasPlayer && hasPlaneswalker) { hasPlaneswalker = true;
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options); }
} else if (hasCard) { }
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer); }
} else if (hasPlayer) {
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options); if (hasPlayer && hasPlaneswalker) {
} return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
} else if (hasCard) {
return null; return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
} } else if (hasPlayer) {
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) { }
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return spells.get(0); return null;
} }
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method"); System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null); return spells.get(0);
} }
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) { protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method"); System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null); return Iterables.getFirst(options, null);
} }
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) { protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method"); System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null); return Iterables.getFirst(options, null);
} }
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) { protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardName is used for " + this.getClass().getName() + ". Consider declaring an overloaded method"); System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
final ICardFace face = Iterables.getFirst(faces, null); }
return face == null ? "" : face.getName();
} public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardName is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
return max; final ICardFace face = Iterables.getFirst(faces, null);
} return face == null ? "" : face.getName();
}
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
return Iterables.getFirst(options, null); public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
} return max;
}
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
return MyRandom.getRandom().nextBoolean(); public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
} return Iterables.getFirst(options, null);
} }
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
return MyRandom.getRandom().nextBoolean();
}
}

View File

@@ -1,172 +1,181 @@
package forge.ai; package forge.ai;
import java.util.Map; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.ImmutableMap; import forge.ai.ability.*;
import com.google.common.collect.Maps; import forge.game.ability.ApiType;
import forge.util.ReflectionUtil;
import forge.ai.ability.*;
import forge.game.ability.ApiType; import java.util.Map;
import forge.util.ReflectionUtil;
public enum SpellApiToAi {
public enum SpellApiToAi { Converter;
Converter;
private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
// Do the extra copy to make an actual EnumMap (faster)
// Do the extra copy to make an actual EnumMap (faster) private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap
private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap .<ApiType, Class<? extends SpellAbilityAi>>builder()
.<ApiType, Class<? extends SpellAbilityAi>>builder() .put(ApiType.Abandon, AlwaysPlayAi.class)
.put(ApiType.Abandon, AlwaysPlayAi.class) .put(ApiType.ActivateAbility, ActivateAbilityAi.class)
.put(ApiType.ActivateAbility, ActivateAbilityAi.class) .put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class) .put(ApiType.AddPhase, AddPhaseAi.class)
.put(ApiType.AddPhase, AddPhaseAi.class) .put(ApiType.AddTurn, AddTurnAi.class)
.put(ApiType.AddTurn, AddTurnAi.class) .put(ApiType.Animate, AnimateAi.class)
.put(ApiType.Animate, AnimateAi.class) .put(ApiType.AnimateAll, AnimateAllAi.class)
.put(ApiType.AnimateAll, AnimateAllAi.class) .put(ApiType.Attach, AttachAi.class)
.put(ApiType.Attach, AttachAi.class) .put(ApiType.Ascend, AlwaysPlayAi.class)
.put(ApiType.Balance, BalanceAi.class) .put(ApiType.AssignGroup, AssignGroupAi.class)
.put(ApiType.BecomeMonarch, AlwaysPlayAi.class) .put(ApiType.Balance, BalanceAi.class)
.put(ApiType.BecomesBlocked, BecomesBlockedAi.class) .put(ApiType.BecomeMonarch, AlwaysPlayAi.class)
.put(ApiType.BidLife, BidLifeAi.class) .put(ApiType.BecomesBlocked, BecomesBlockedAi.class)
.put(ApiType.Bond, BondAi.class) .put(ApiType.BidLife, BidLifeAi.class)
.put(ApiType.Branch, AlwaysPlayAi.class) .put(ApiType.Bond, BondAi.class)
.put(ApiType.ChangeTargets, ChangeTargetsAi.class) .put(ApiType.Branch, AlwaysPlayAi.class)
.put(ApiType.ChangeZone, ChangeZoneAi.class) .put(ApiType.ChangeCombatants, CannotPlayAi.class)
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class) .put(ApiType.ChangeTargets, ChangeTargetsAi.class)
.put(ApiType.Charm, CharmAi.class) .put(ApiType.ChangeZone, ChangeZoneAi.class)
.put(ApiType.ChooseCard, ChooseCardAi.class) .put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
.put(ApiType.ChooseColor, ChooseColorAi.class) .put(ApiType.Charm, CharmAi.class)
.put(ApiType.ChooseDirection, ChooseDirectionAi.class) .put(ApiType.ChooseCard, ChooseCardAi.class)
.put(ApiType.ChooseNumber, ChooseNumberAi.class) .put(ApiType.ChooseColor, ChooseColorAi.class)
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class) .put(ApiType.ChooseDirection, ChooseDirectionAi.class)
.put(ApiType.ChooseSource, ChooseSourceAi.class) .put(ApiType.ChooseNumber, ChooseNumberAi.class)
.put(ApiType.ChooseType, ChooseTypeAi.class) .put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
.put(ApiType.Clash, ClashAi.class) .put(ApiType.ChooseSource, ChooseSourceAi.class)
.put(ApiType.Cleanup, AlwaysPlayAi.class) .put(ApiType.ChooseType, ChooseTypeAi.class)
.put(ApiType.Clone, CloneAi.class) .put(ApiType.Clash, ClashAi.class)
.put(ApiType.CopyPermanent, CopyPermanentAi.class) .put(ApiType.Cleanup, AlwaysPlayAi.class)
.put(ApiType.CopySpellAbility, CopySpellAbilityAi.class) .put(ApiType.Clone, CloneAi.class)
.put(ApiType.ControlPlayer, CannotPlayAi.class) .put(ApiType.CopyPermanent, CopyPermanentAi.class)
.put(ApiType.ControlSpell, CannotPlayAi.class) .put(ApiType.CopySpellAbility, CopySpellAbilityAi.class)
.put(ApiType.Counter, CounterAi.class) .put(ApiType.ControlPlayer, CannotPlayAi.class)
.put(ApiType.DamageAll, DamageAllAi.class) .put(ApiType.ControlSpell, CannotPlayAi.class)
.put(ApiType.DealDamage, DamageDealAi.class) .put(ApiType.Counter, CounterAi.class)
.put(ApiType.Debuff, DebuffAi.class) .put(ApiType.DamageAll, DamageAllAi.class)
.put(ApiType.DeclareCombatants, CannotPlayAi.class) .put(ApiType.DealDamage, DamageDealAi.class)
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class) .put(ApiType.Debuff, DebuffAi.class)
.put(ApiType.Destroy, DestroyAi.class) .put(ApiType.DeclareCombatants, CannotPlayAi.class)
.put(ApiType.DestroyAll, DestroyAllAi.class) .put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
.put(ApiType.Dig, DigAi.class) .put(ApiType.Destroy, DestroyAi.class)
.put(ApiType.DigUntil, DigUntilAi.class) .put(ApiType.DestroyAll, DestroyAllAi.class)
.put(ApiType.Discard, DiscardAi.class) .put(ApiType.Dig, DigAi.class)
.put(ApiType.DrainMana, DrainManaAi.class) .put(ApiType.DigUntil, DigUntilAi.class)
.put(ApiType.Draw, DrawAi.class) .put(ApiType.Discard, DiscardAi.class)
.put(ApiType.EachDamage, DamageEachAi.class) .put(ApiType.DrainMana, DrainManaAi.class)
.put(ApiType.Effect, EffectAi.class) .put(ApiType.Draw, DrawAi.class)
.put(ApiType.Encode, EncodeAi.class) .put(ApiType.EachDamage, DamageEachAi.class)
.put(ApiType.EndTurn, EndTurnAi.class) .put(ApiType.Effect, EffectAi.class)
.put(ApiType.ExchangeLife, LifeExchangeAi.class) .put(ApiType.Encode, EncodeAi.class)
.put(ApiType.ExchangeControl, ControlExchangeAi.class) .put(ApiType.EndTurn, EndTurnAi.class)
.put(ApiType.ExchangeControlVariant, CannotPlayAi.class) .put(ApiType.ExchangeLife, LifeExchangeAi.class)
.put(ApiType.ExchangePower, PowerExchangeAi.class) .put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class)
.put(ApiType.ExchangeZone, ZoneExchangeAi.class) .put(ApiType.ExchangeControl, ControlExchangeAi.class)
.put(ApiType.Fight, FightAi.class) .put(ApiType.ExchangeControlVariant, CannotPlayAi.class)
.put(ApiType.FlipACoin, FlipACoinAi.class) .put(ApiType.ExchangePower, PowerExchangeAi.class)
.put(ApiType.Fog, FogAi.class) .put(ApiType.ExchangeZone, ZoneExchangeAi.class)
.put(ApiType.GainControl, ControlGainAi.class) .put(ApiType.Explore, ExploreAi.class)
.put(ApiType.GainLife, LifeGainAi.class) .put(ApiType.Fight, FightAi.class)
.put(ApiType.GainOwnership, CannotPlayAi.class) .put(ApiType.FlipACoin, FlipACoinAi.class)
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class) .put(ApiType.Fog, FogAi.class)
.put(ApiType.Goad, GoadAi.class) .put(ApiType.GainControl, ControlGainAi.class)
.put(ApiType.Haunt, HauntAi.class) .put(ApiType.GainLife, LifeGainAi.class)
.put(ApiType.LoseLife, LifeLoseAi.class) .put(ApiType.GainOwnership, CannotPlayAi.class)
.put(ApiType.LosesGame, GameLossAi.class) .put(ApiType.GameDrawn, CannotPlayAi.class)
.put(ApiType.Mana, ManaEffectAi.class) .put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
.put(ApiType.ManaReflected, CannotPlayAi.class) .put(ApiType.Goad, GoadAi.class)
.put(ApiType.Manifest, ManifestAi.class) .put(ApiType.Haunt, HauntAi.class)
.put(ApiType.Meld, MeldAi.class) .put(ApiType.LoseLife, LifeLoseAi.class)
.put(ApiType.Mill, MillAi.class) .put(ApiType.LosesGame, GameLossAi.class)
.put(ApiType.MoveCounter, CountersMoveAi.class) .put(ApiType.Mana, ManaEffectAi.class)
.put(ApiType.MultiplePiles, CannotPlayAi.class) .put(ApiType.ManaReflected, CannotPlayAi.class)
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class) .put(ApiType.Manifest, ManifestAi.class)
.put(ApiType.MustAttack, MustAttackAi.class) .put(ApiType.Meld, MeldAi.class)
.put(ApiType.MustBlock, MustBlockAi.class) .put(ApiType.Mill, MillAi.class)
.put(ApiType.NameCard, ChooseCardNameAi.class) .put(ApiType.MoveCounter, CountersMoveAi.class)
.put(ApiType.NoteCounters, AlwaysPlayAi.class) .put(ApiType.MultiplePiles, CannotPlayAi.class)
.put(ApiType.PeekAndReveal, PeekAndRevealAi.class) .put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
.put(ApiType.PermanentCreature, PermanentCreatureAi.class) .put(ApiType.MustAttack, MustAttackAi.class)
.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class) .put(ApiType.MustBlock, MustBlockAi.class)
.put(ApiType.Phases, PhasesAi.class) .put(ApiType.NameCard, ChooseCardNameAi.class)
.put(ApiType.Planeswalk, AlwaysPlayAi.class) .put(ApiType.NoteCounters, AlwaysPlayAi.class)
.put(ApiType.Play, PlayAi.class) .put(ApiType.PeekAndReveal, PeekAndRevealAi.class)
.put(ApiType.PlayLandVariant, CannotPlayAi.class) .put(ApiType.PermanentCreature, PermanentCreatureAi.class)
.put(ApiType.Poison, PoisonAi.class) .put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class)
.put(ApiType.PreventDamage, DamagePreventAi.class) .put(ApiType.Phases, PhasesAi.class)
.put(ApiType.PreventDamageAll, DamagePreventAllAi.class) .put(ApiType.Planeswalk, AlwaysPlayAi.class)
.put(ApiType.Proliferate, CountersProliferateAi.class) .put(ApiType.Play, PlayAi.class)
.put(ApiType.Protection, ProtectAi.class) .put(ApiType.PlayLandVariant, CannotPlayAi.class)
.put(ApiType.ProtectionAll, ProtectAllAi.class) .put(ApiType.Poison, PoisonAi.class)
.put(ApiType.Pump, PumpAi.class) .put(ApiType.PreventDamage, DamagePreventAi.class)
.put(ApiType.PumpAll, PumpAllAi.class) .put(ApiType.PreventDamageAll, DamagePreventAllAi.class)
.put(ApiType.PutCounter, CountersPutAi.class) .put(ApiType.Proliferate, CountersProliferateAi.class)
.put(ApiType.PutCounterAll, CountersPutAllAi.class) .put(ApiType.Protection, ProtectAi.class)
.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class) .put(ApiType.ProtectionAll, ProtectAllAi.class)
.put(ApiType.Regenerate, RegenerateAi.class) .put(ApiType.Pump, PumpAi.class)
.put(ApiType.RegenerateAll, RegenerateAllAi.class) .put(ApiType.PumpAll, PumpAllAi.class)
.put(ApiType.RemoveCounter, CountersRemoveAi.class) .put(ApiType.PutCounter, CountersPutAi.class)
.put(ApiType.RemoveCounterAll, CannotPlayAi.class) .put(ApiType.PutCounterAll, CountersPutAllAi.class)
.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class) .put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class)
.put(ApiType.ReorderZone, AlwaysPlayAi.class) .put(ApiType.Regenerate, RegenerateAi.class)
.put(ApiType.Repeat, RepeatAi.class) .put(ApiType.RegenerateAll, RegenerateAllAi.class)
.put(ApiType.RepeatEach, RepeatEachAi.class) .put(ApiType.Regeneration, AlwaysPlayAi.class)
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class) .put(ApiType.RemoveCounter, CountersRemoveAi.class)
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class) .put(ApiType.RemoveCounterAll, CannotPlayAi.class)
.put(ApiType.RestartGame, RestartGameAi.class) .put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class)
.put(ApiType.Reveal, RevealAi.class) .put(ApiType.ReorderZone, AlwaysPlayAi.class)
.put(ApiType.RevealHand, RevealHandAi.class) .put(ApiType.Repeat, RepeatAi.class)
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class) .put(ApiType.RepeatEach, RepeatEachAi.class)
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class) .put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class) .put(ApiType.ReplaceDamage, AlwaysPlayAi.class)
.put(ApiType.Sacrifice, SacrificeAi.class) .put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
.put(ApiType.SacrificeAll, SacrificeAllAi.class) .put(ApiType.RestartGame, RestartGameAi.class)
.put(ApiType.Scry, ScryAi.class) .put(ApiType.Reveal, RevealAi.class)
.put(ApiType.SetInMotion, AlwaysPlayAi.class) .put(ApiType.RevealHand, RevealHandAi.class)
.put(ApiType.SetLife, LifeSetAi.class) .put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
.put(ApiType.SetState, SetStateAi.class) .put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
.put(ApiType.Shuffle, ShuffleAi.class) .put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
.put(ApiType.SkipTurn, SkipTurnAi.class) .put(ApiType.Sacrifice, SacrificeAi.class)
.put(ApiType.StoreMap, StoreMapAi.class) .put(ApiType.SacrificeAll, SacrificeAllAi.class)
.put(ApiType.StoreSVar, StoreSVarAi.class) .put(ApiType.Scry, ScryAi.class)
.put(ApiType.Tap, TapAi.class) .put(ApiType.SetInMotion, AlwaysPlayAi.class)
.put(ApiType.TapAll, TapAllAi.class) .put(ApiType.SetLife, LifeSetAi.class)
.put(ApiType.TapOrUntap, TapOrUntapAi.class) .put(ApiType.SetState, SetStateAi.class)
.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class) .put(ApiType.Shuffle, ShuffleAi.class)
.put(ApiType.Token, TokenAi.class) .put(ApiType.SkipTurn, SkipTurnAi.class)
.put(ApiType.TwoPiles, TwoPilesAi.class) .put(ApiType.StoreMap, StoreMapAi.class)
.put(ApiType.Unattach, CannotPlayAi.class) .put(ApiType.StoreSVar, StoreSVarAi.class)
.put(ApiType.UnattachAll, UnattachAllAi.class) .put(ApiType.Surveil, SurveilAi.class)
.put(ApiType.Untap, UntapAi.class) .put(ApiType.Tap, TapAi.class)
.put(ApiType.UntapAll, UntapAllAi.class) .put(ApiType.TapAll, TapAllAi.class)
.put(ApiType.Vote, VoteAi.class) .put(ApiType.TapOrUntap, TapOrUntapAi.class)
.put(ApiType.WinsGame, GameWinAi.class) .put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class)
.put(ApiType.Token, TokenAi.class)
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class) .put(ApiType.TwoPiles, TwoPilesAi.class)
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class) .put(ApiType.Unattach, CannotPlayAi.class)
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class) .put(ApiType.UnattachAll, UnattachAllAi.class)
.build()); .put(ApiType.Untap, UntapAi.class)
.put(ApiType.UntapAll, UntapAllAi.class)
public SpellAbilityAi get(final ApiType api) { .put(ApiType.Vote, VoteAi.class)
SpellAbilityAi result = apiToInstance.get(api); .put(ApiType.WinsGame, GameWinAi.class)
if (null == result) {
Class<? extends SpellAbilityAi> clz = apiToClass.get(api); .put(ApiType.DamageResolve, AlwaysPlayAi.class)
if (null == clz) { .put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
System.err.println("No AI assigned for API: " + api); .put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
clz = CannotPlayAi.class; .put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
} .build());
result = ReflectionUtil.makeDefaultInstanceOf(clz);
apiToInstance.put(api, result); public SpellAbilityAi get(final ApiType api) {
} SpellAbilityAi result = apiToInstance.get(api);
return result; if (null == result) {
} Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
} if (null == clz) {
System.err.println("No AI assigned for API: " + api);
clz = CannotPlayAi.class;
}
result = ReflectionUtil.makeDefaultInstanceOf(clz);
apiToInstance.put(api, result);
}
return result;
}
}

View File

@@ -1,100 +1,101 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtil;
import forge.game.ability.AbilityUtils; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.ability.AbilityUtils;
import forge.game.card.CardLists; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.card.CardLists;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom; import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random; import java.util.List;
import java.util.Map;
public class ActivateAbilityAi extends SpellAbilityAi {
public class ActivateAbilityAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { @Override
// AI cannot use this properly until he can use SAs during Humans turn protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getOpponent(); final Card source = sa.getHostCard();
final Random r = MyRandom.getRandom(); final Player opp = ComputerUtil.getOpponentFor(ai);
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card")); List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
if (list.isEmpty()) { if (list.isEmpty()) {
return false; return false;
} }
if (tgt == null) { if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) { if (!defined.contains(opp)) {
return false; return false;
} }
} else { } else {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(opp); sa.getTargets().add(opp);
} }
return randomReturn; return randomReturn;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { 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 TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
if (null == tgt) { if (null == tgt) {
if (mandatory) { if (mandatory) {
return true; return true;
} else { } else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) { if (!defined.contains(opp)) {
return false; return false;
} }
} }
return true; return true;
} else { } else {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(opp); sa.getTargets().add(opp);
} }
return true; return true;
} }
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI cannot use this properly until he can use SAs during Humans turn // AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
boolean randomReturn = true; boolean randomReturn = true;
if (tgt == null) { if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (defined.contains(ai)) { if (defined.contains(ai)) {
return false; return false;
} }
} else { } else {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(ai.getOpponent()); sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
} }
return randomReturn; return randomReturn;
} }
@Override @Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) { public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
return spells.get(0); Map<String, Object> params) {
} return spells.get(0);
} }
}

View File

@@ -1,18 +1,18 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
* *
*/ */
public class AddPhaseAi extends SpellAbilityAi { public class AddPhaseAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; return false;
} }
} }

View File

@@ -1,16 +1,22 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
public class AlwaysPlayAi extends SpellAbilityAi {
/* (non-Javadoc) public class AlwaysPlayAi extends SpellAbilityAi {
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) /* (non-Javadoc)
*/ * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
@Override */
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { @Override
return true; protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
} return true;
} }
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,19 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class AnimateAllAi extends SpellAbilityAi { public class AnimateAllAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; return false;
} // end animateAllCanPlayAI() } // end animateAllCanPlayAI()
@Override @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false; return mandatory;
} }
} // end class AbilityFactoryAnimate } // end class AbilityFactoryAnimate

View File

@@ -0,0 +1,33 @@
package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Iterables;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class AssignGroupAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// TODO: Currently this AI relies on the card-specific limiting hints (NeedsToPlay / NeedsToPlayVar),
// otherwise the AI considers the card playable.
return true;
}
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
final String logic = sa.getParamOrDefault("AILogic", "");
if (logic.equals("FriendOrFoe")) {
if (params.containsKey("Affected") && spells.size() >= 2) {
Player t = (Player) params.get("Affected");
return spells.get(player.isOpponentOf(t) ? 1 : 0);
}
}
return Iterables.getFirst(spells, null);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,48 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtil;
import forge.game.card.CardCollectionView; import forge.ai.SpellAbilityAi;
import forge.game.card.CardLists; import forge.game.card.CardCollectionView;
import forge.game.card.CardPredicates; import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.card.CardPredicates;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.spellability.SpellAbility;
import forge.util.MyRandom; import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class BalanceAi extends SpellAbilityAi {
@Override public class BalanceAi extends SpellAbilityAi {
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { @Override
String logic = sa.getParam("AILogic"); protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
int diff = 0;
// TODO Add support for multiplayer logic int diff = 0;
final Player opp = aiPlayer.getOpponent(); // TODO Add support for multiplayer logic
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield); final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield); final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
if ("BalanceCreaturesAndLands".equals(logic)) {
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting if ("BalanceCreaturesAndLands".equals(logic)) {
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() - // Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size(); diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() - CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size()); diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
} CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
else if ("BalancePermanents".equals(logic)) { }
// Don't cast if you have to sacrifice permanents else if ("BalancePermanents".equals(logic)) {
diff += humPerms.size() - compPerms.size(); // Don't cast if you have to sacrifice permanents
} diff += humPerms.size() - compPerms.size();
}
if (diff < 0) {
// Don't sacrifice permanents even if opponent has a ton of cards in hand if (diff < 0) {
return false; // Don't sacrifice permanents even if opponent has a ton of cards in hand
} return false;
}
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
final CardCollectionView compHand = aiPlayer.getCardsIn(ZoneType.Hand); final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
diff += 0.5 * (humHand.size() - compHand.size()); final CardCollectionView compHand = aiPlayer.getCardsIn(ZoneType.Hand);
diff += 0.5 * (humHand.size() - compHand.size());
// Larger differential == more chance to actually cast this spell
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10; // Larger differential == more chance to actually cast this spell
} return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
} }
}

View File

@@ -1,70 +1,71 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.phase.PhaseType; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.phase.PhaseType;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class BecomesBlockedAi extends SpellAbilityAi {
@Override public class BecomesBlockedAi extends SpellAbilityAi {
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { @Override
final Card source = sa.getHostCard(); protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard();
final Game game = aiPlayer.getGame(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = aiPlayer.getGame();
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) { if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
return false; || !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
} return false;
}
if (tgt != null) {
sa.resetTargets(); if (tgt != null) {
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents()); sa.resetTargets();
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa); CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
list = CardLists.getTargetableCards(list, sa); list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
list = CardLists.getNotKeyword(list, "Trample"); list = CardLists.getTargetableCards(list, sa);
list = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card choice = null; while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card choice = null;
if (list.isEmpty()) {
return false; if (list.isEmpty()) {
} return false;
}
choice = ComputerUtilCard.getBestCreatureAI(list);
choice = ComputerUtilCard.getBestCreatureAI(list);
if (choice == null) { // can't find anything left
return false; if (choice == null) { // can't find anything left
} return false;
}
list.remove(choice);
sa.getTargets().add(choice); list.remove(choice);
} sa.getTargets().add(choice);
} }
return true; }
} return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { @Override
// TODO - implement AI public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return false; // TODO - implement AI
} return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { @Override
boolean chance; protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false; // TODO - implement AI
chance = false;
return chance;
} return chance;
} }
}

View File

@@ -1,57 +1,58 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtilCard;
import forge.game.Game; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.Game;
import forge.game.card.CardFactoryUtil; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardFactoryUtil;
import forge.game.player.Player; import forge.game.card.CardLists;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom; import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class BidLifeAi extends SpellAbilityAi {
public class BidLifeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { @Override
final Card source = sa.getHostCard(); protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Game game = source.getGame(); final Card source = sa.getHostCard();
TargetRestrictions tgt = sa.getTargetRestrictions(); final Game game = source.getGame();
if (tgt != null) { TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets(); if (tgt != null) {
if (tgt.canTgtCreature()) { sa.resetTargets();
List<Card> list = CardLists.getTargetableCards(aiPlayer.getOpponent().getCardsIn(ZoneType.Battlefield), sa); if (tgt.canTgtCreature()) {
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa); List<Card> list = CardLists.getTargetableCards(ComputerUtil.getOpponentFor(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
if (list.isEmpty()) { list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
return false; if (list.isEmpty()) {
} return false;
Card c = ComputerUtilCard.getBestCreatureAI(list); }
if (sa.canTarget(c)) { Card c = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(c); if (sa.canTarget(c)) {
} else { sa.getTargets().add(c);
return false; } else {
} return false;
} else if (tgt.getZone().contains(ZoneType.Stack)) { }
if (game.getStack().isEmpty()) { } else if (tgt.getZone().contains(ZoneType.Stack)) {
return false; if (game.getStack().isEmpty()) {
} return false;
final SpellAbility topSA = game.getStack().peekAbility(); }
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || aiPlayer.equals(topSA.getActivatingPlayer())) { final SpellAbility topSA = game.getStack().peekAbility();
return false; if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
} return false;
if (sa.canTargetSpellAbility(topSA)) { }
sa.getTargets().add(topSA); if (sa.canTargetSpellAbility(topSA)) {
} else { sa.getTargets().add(topSA);
return false; } else {
} return false;
} }
} }
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); }
return chance; boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
} return chance;
}
}
}

View File

@@ -1,56 +1,61 @@
/* /*
* Forge: Play Magic: the Gathering. * Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team * Copyright (C) 2011 Forge Team
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
/** /**
* <p> * <p>
* AbilityFactoryBond class. * AbilityFactoryBond class.
* </p> * </p>
* *
* @author Forge * @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $ * @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/ */
public final class BondAi extends SpellAbilityAi { public final class BondAi extends SpellAbilityAi {
/** /**
* <p> * <p>
* bondCanPlayAI. * bondCanPlayAI.
* </p> * </p>
* @param sa * @param aiPlayer
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.player.Player} object.
* @param af * @param sa
* a {@link forge.game.ability.AbilityFactory} object. * a {@link forge.game.spellability.SpellAbility} object.
* *
* @return a boolean. * @return a boolean.
*/ */
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true; return true;
} // end bondCanPlayAI() } // end bondCanPlayAI()
@Override @Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestCreatureAI(options); return ComputerUtilCard.getBestCreatureAI(options);
} }
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
return true;
}
}

View File

@@ -1,44 +1,46 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import java.util.List; import java.util.List;
import java.util.Map;
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) /* (non-Javadoc)
*/ * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
@Override */
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { @Override
return false; protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
} return false;
}
/**
* <p> /**
* copySpellTriggerAI. * <p>
* </p> * copySpellTriggerAI.
* @param sa * </p>
* a {@link forge.game.spellability.SpellAbility} object. * @param sa
* @param mandatory * a {@link forge.game.spellability.SpellAbility} object.
* a boolean. * @param mandatory
* @param af * a boolean.
* a {@link forge.game.ability.AbilityFactory} object. * @param af
* * a {@link forge.game.ability.AbilityFactory} object.
* @return a boolean. *
*/ * @return a boolean.
@Override */
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { @Override
return false; protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
} return false;
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) { @Override
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
return spells.get(0); Map<String, Object> params) {
} // This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
} return spells.get(0);
}
}

View File

@@ -1,24 +1,24 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class CannotPlayAi extends SpellAbilityAi { public class CannotPlayAi extends SpellAbilityAi {
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; return false;
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player) * @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/ */
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa); return canPlayAI(aiPlayer, sa);
} }
} }

View File

@@ -1,93 +1,93 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class ChangeTargetsAi extends SpellAbilityAi { public class ChangeTargetsAi extends SpellAbilityAi {
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, * @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility) * forge.game.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) { protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Game game = sa.getHostCard().getGame(); final Game game = sa.getHostCard().getGame();
final SpellAbility topSa = game.getStack().isEmpty() ? null final SpellAbility topSa = game.getStack().isEmpty() ? null
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa); : ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
if ("Self".equals(sa.getParam("DefinedMagnet"))) { if ("Self".equals(sa.getParam("DefinedMagnet"))) {
return doSpellMagnet(sa, topSa, ai); return doSpellMagnet(sa, topSa, ai);
} }
// The AI can't otherwise play this ability, but should at least not // The AI can't otherwise play this ability, but should at least not
// miss mandatory activations (e.g. triggers). // miss mandatory activations (e.g. triggers).
return sa.isMandatory(); return sa.isMandatory();
} }
private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) { private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
// For cards like Spellskite that retarget spells to itself // For cards like Spellskite that retarget spells to itself
if (topSa == null) { if (topSa == null) {
// nothing on stack, so nothing to target // nothing on stack, so nothing to target
return false; return false;
} }
if (sa.getTargets().getNumTargeted() != 0) { if (sa.getTargets().getNumTargeted() != 0) {
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed // something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
return true; return true;
} }
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) { if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
// if this does not target at all or already targets host, no need to redirect it again // if this does not target at all or already targets host, no need to redirect it again
return false; return false;
} }
for (Card tgt : topSa.getTargets().getTargetCards()) { for (Card tgt : topSa.getTargets().getTargetCards()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) { if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites), // We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
// no need to retarget again to another one // no need to retarget again to another one
return false; return false;
} }
} }
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) { if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
// make sure not to redirect our own abilities // make sure not to redirect our own abilities
return false; return false;
} }
if (!topSa.canTarget(sa.getHostCard())) { if (!topSa.canTarget(sa.getHostCard())) {
// don't try targeting it if we can't legally target the host card with it in the first place // don't try targeting it if we can't legally target the host card with it in the first place
return false; return false;
} }
if (!sa.canTarget(topSa)) { if (!sa.canTarget(topSa)) {
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face) // don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
return false; return false;
} }
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) { if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana(); ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
int payDamage = manaCost.getPhyrexianCount() * 2; int payDamage = manaCost.getPhyrexianCount() * 2;
// e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U // e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U
int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer); int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer);
ManaCost normalizedMana = manaCost.getNormalizedMana(); ManaCost normalizedMana = manaCost.getNormalizedMana();
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer); boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
&& topSa.getTargets().getTargets().contains(aiPlayer)) { && topSa.getTargets().getTargets().contains(aiPlayer)) {
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life // do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
return false; return false;
} }
} }
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(topSa); sa.getTargets().add(topSa);
return true; return true;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,405 +1,464 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Collections; import com.google.common.collect.Iterables;
import java.util.Random; import com.google.common.collect.Lists;
import forge.ai.*;
import com.google.common.collect.Iterables; import forge.game.Game;
import com.google.common.collect.Lists; import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.ai.AiPlayerPredicates; import forge.game.cost.Cost;
import forge.ai.ComputerUtilAbility; import forge.game.phase.PhaseType;
import forge.ai.ComputerUtilCard; import forge.game.player.Player;
import forge.ai.ComputerUtilCost; import forge.game.player.PlayerActionConfirmMode;
import forge.ai.SpecialCardAi; import forge.game.player.PlayerCollection;
import forge.ai.SpellAbilityAi; import forge.game.player.PlayerPredicates;
import forge.game.Game; import forge.game.spellability.SpellAbility;
import forge.game.ability.AbilityUtils; import forge.game.zone.ZoneType;
import forge.game.card.Card; import forge.util.MyRandom;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import java.util.Collections;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates; public class ChangeZoneAllAi extends SpellAbilityAi {
import forge.game.cost.Cost; @Override
import forge.game.phase.PhaseType; protected boolean canPlayAI(Player ai, SpellAbility sa) {
import forge.game.player.Player; // Change Zone All, can be any type moving from one zone to another
import forge.game.player.PlayerActionConfirmMode; final Cost abCost = sa.getPayCosts();
import forge.game.player.PlayerPredicates; final Card source = sa.getHostCard();
import forge.game.spellability.SpellAbility; final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
import forge.game.zone.ZoneType; final Game game = ai.getGame();
import forge.util.MyRandom; final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
public class ChangeZoneAllAi extends SpellAbilityAi {
@Override if (abCost != null) {
protected boolean canPlayAI(Player ai, SpellAbility sa) { // AI currently disabled for these costs
// Change Zone All, can be any type moving from one zone to another if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
final Cost abCost = sa.getPayCosts(); return false;
final Card source = sa.getHostCard(); }
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame(); if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination")); boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (!aiLogicAllowsDiscard) {
if (abCost != null) { return false;
// AI currently disabled for these costs }
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) { }
return false; }
}
// prevent run-away activations - first time will always return true
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return false;
} // TODO targeting with ChangeZoneAll
} // really two types of targeting.
// Target Player has all their types change zones
final Random r = MyRandom.getRandom(); // or target permanent and do something relative to that permanent
// prevent run-away activations - first time will always return true // ex. "Return all Auras attached to target"
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); // ex. "Return all blocking/blocked by target creature"
// TODO targeting with ChangeZoneAll CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
// really two types of targeting. CardCollectionView computerType = ai.getCardsIn(origin);
// Target Player has all their types change zones
// or target permanent and do something relative to that permanent // Ugin check need to be done before filterListByType because of ChosenX
// ex. "Return all Auras attached to target" // Ugin AI: always try to sweep before considering +1
// ex. "Return all blocking/blocked by target creature" if (sourceName.equals("Ugin, the Spirit Dragon")) {
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents()); }
CardCollectionView computerType = ai.getCardsIn(origin);
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
// Ugin check need to be done before filterListByType because of ChosenX computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
// Ugin AI: always try to sweep before considering +1
if (sourceName.equals("Ugin, the Spirit Dragon")) { if ("LivingDeath".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType); // Living Death AI
} return SpecialCardAi.LivingDeath.consider(ai, sa);
} else if ("Timetwister".equals(sa.getParam("AILogic"))) {
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa); // Timetwister AI
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa); return SpecialCardAi.Timetwister.consider(ai, sa);
} else if ("RetDiscardedThisTurn".equals(sa.getParam("AILogic"))) {
// Living Death AI // e.g. Shadow of the Grave
if ("LivingDeath".equals(sa.getParam("AILogic"))) { return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
return SpecialCardAi.LivingDeath.consider(ai, sa); } else if ("ExileGraveyards".equals(sa.getParam("AILogic"))) {
} for (Player opp : ai.getOpponents()) {
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
// Timetwister AI CardCollection creats = CardLists.filter(cardsGY, CardPredicates.Presets.CREATURES);
if ("Timetwister".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.Timetwister.consider(ai, sa); if (opp.hasDelirium() || opp.hasThreshold() || creats.size() >= 5) {
} return true;
}
// TODO improve restrictions on when the AI would want to use this }
// spBounceAll has some AI we can compare to. return false;
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) { } else if ("ManifestCreatsFromGraveyard".equals(sa.getParam("AILogic"))) {
if (!sa.usesTargeting()) { PlayerCollection players = new PlayerCollection();
// TODO: improve logic for non-targeted SAs of this type (most are currently RemAIDeck, e.g. Memory Jar, Timetwister) players.addAll(ai.getOpponents());
return true; players.add(ai);
} else { int maxSize = 1;
// search targetable Opponents for (Player player : players) {
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)); Player bestTgt = null;
if (player.canBeTargetedBy(sa)) {
// get the one with the most handsize CardCollectionView cardsGY = CardLists.filter(player.getCardsIn(ZoneType.Graveyard),
Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin)); CardPredicates.Presets.CREATURES);
if (cardsGY.size() > maxSize) {
// set the target maxSize = cardsGY.size();
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) { bestTgt = player;
sa.resetTargets(); }
sa.getTargets().add(oppTarget); }
} else {
return false; if (bestTgt != null) {
} sa.resetTargets();
} sa.getTargets().add(bestTgt);
} else if (origin.equals(ZoneType.Battlefield)) { return true;
// this statement is assuming the AI is trying to use this spell }
// offensively }
// if the AI is using it defensively, then something else needs to return false;
// occur }
// if only creatures are affected evaluate both lists and pass only
// if human creatures are more valuable // TODO improve restrictions on when the AI would want to use this
if (sa.usesTargeting()) { // spBounceAll has some AI we can compare to.
// search targetable Opponents if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), if (!sa.usesTargeting()) {
PlayerPredicates.isTargetableBy(sa)); // TODO: improve logic for non-targeted SAs of this type (most are currently RemAIDeck, e.g. Memory Jar)
return true;
// get the one with the most in graveyard } else {
// zone is visible so evaluate which would be hurt the most // search targetable Opponents
Player oppTarget = Collections.max(Lists.newArrayList(oppList), final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa));
PlayerPredicates.compareByZoneSize(origin));
// get the one with the most handsize
// set the target Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin));
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets(); // set the target
sa.getTargets().add(oppTarget); if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
} else { sa.resetTargets();
return false; sa.getTargets().add(oppTarget);
} } else {
computerType = new CardCollection(); return false;
} }
if ((CardLists.getNotType(oppType, "Creature").size() == 0) }
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) { } else if (origin.equals(ZoneType.Battlefield)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + 200) >= ComputerUtilCard // this statement is assuming the AI is trying to use this spell
.evaluateCreatureList(oppType)) { // offensively
return false; // if the AI is using it defensively, then something else needs to
} // occur
} // otherwise evaluate both lists by CMC and pass only if human // if only creatures are affected evaluate both lists and pass only
// permanents are more valuable // if human creatures are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + 3) >= ComputerUtilCard if (sa.usesTargeting()) {
.evaluatePermanentList(oppType)) { // search targetable Opponents
return false; final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
} PlayerPredicates.isTargetableBy(sa));
// Don't cast during main1? // get the one with the most in graveyard
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) { // zone is visible so evaluate which would be hurt the most
return false; Player oppTarget = Collections.max(Lists.newArrayList(oppList),
} PlayerPredicates.compareByZoneSize(origin));
} else if (origin.equals(ZoneType.Graveyard)) {
if (sa.usesTargeting()) { // set the target
// search targetable Opponents if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), sa.resetTargets();
PlayerPredicates.isTargetableBy(sa)); sa.getTargets().add(oppTarget);
} else {
if (Iterables.isEmpty(oppList)) { return false;
return false; }
} computerType = new CardCollection();
}
// get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
Player oppTarget = Collections.max(Lists.newArrayList(oppList), int nonCreatureEvalThreshold = 3; // CMC difference
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa)); if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
// set the target if (destination == ZoneType.Hand) {
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF);
sa.resetTargets(); nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF);
sa.getTargets().add(oppTarget); } else {
} else { creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF);
return false; nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF);
} }
} }
} else if (origin.equals(ZoneType.Exile)) {
String logic = sa.getParam("AILogic"); // mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's
// creatures are better in value
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) { if ((CardLists.getNotType(oppType, "Creature").size() == 0)
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size(); && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
int curHandSize = ai.getCardsIn(ZoneType.Hand).size(); if (game.getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
// minimum card advantage unless the hand will be fully reloaded && game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0; // Life is in serious danger, return all creatures from the battlefield to wherever
// so they don't deal lethal damage
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize()); return true;
} }
} else if (origin.equals(ZoneType.Stack)) { }
// time stop can do something like this: if ((ComputerUtilCard.evaluateCreatureList(computerType) + creatureEvalThreshold) >= ComputerUtilCard
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip .evaluateCreatureList(oppType)) {
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup return false;
// otherwise, this situation doesn't exist }
return false; } // mass zone change for non-creatures: evaluate both lists by CMC and pass only if human
} // permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + nonCreatureEvalThreshold) >= ComputerUtilCard
if (destination.equals(ZoneType.Battlefield)) { .evaluatePermanentList(oppType)) {
if (sa.getParam("GainControl") != null) { return false;
// Check if the cards are valuable enough }
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) { // Don't cast during main1?
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) {
.evaluateCreatureList(oppType)) < 400) { return false;
return false; }
} } else if (origin.equals(ZoneType.Graveyard)) {
} // otherwise evaluate both lists by CMC and pass only if human if (sa.usesTargeting()) {
// permanents are less valuable // search targetable Opponents
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
.evaluatePermanentList(oppType)) < 6) { PlayerPredicates.isTargetableBy(sa));
return false;
} if (Iterables.isEmpty(oppList)) {
} else { return false;
// don't activate if human gets more back than AI does }
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) { // get the one with the most in graveyard
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard // zone is visible so evaluate which would be hurt the most
.evaluateCreatureList(oppType) + 100)) { Player oppTarget = Collections.max(Lists.newArrayList(oppList),
return false; AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
}
} // otherwise evaluate both lists by CMC and pass only if human // set the target
// permanents are less valuable if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard sa.resetTargets();
.evaluatePermanentList(oppType) + 2)) { sa.getTargets().add(oppTarget);
return false; } else {
} return false;
} }
} }
} else if (origin.equals(ZoneType.Exile)) {
return (((r.nextFloat() < .8) || sa.isTrigger()) && chance); String logic = sa.getParam("AILogic");
}
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
/** int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
* <p> int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
* changeZoneAllPlayDrawbackAI.
* </p> // minimum card advantage unless the hand will be fully reloaded
* @param sa int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
* a {@link forge.game.spellability.SpellAbility} object.
* @param af if (numExiledWithSrc > curHandSize) {
* a {@link forge.game.ability.AbilityFactory} object. if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
* // Try to gain some card advantage if the card will die anyway
* @return a boolean. // TODO: ideally, should evaluate the hand value and not discard good hands to it
*/ return true;
@Override }
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { }
// if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something: return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
}
return true; } else if (origin.equals(ZoneType.Stack)) {
} // time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
/* (non-Javadoc) // DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) // otherwise, this situation doesn't exist
*/ return false;
@Override }
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
final Card source = sa.getHostCard(); if (destination.equals(ZoneType.Battlefield)) {
final String hostName = source.getName(); if (sa.getParam("GainControl") != null) {
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0); // Check if the cards are valuable enough
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
if (hostName.equals("Dawnbreak Reclaimer")) { && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
final CardCollectionView cards = AbilityUtils.filterListByType(ai.getGame().getCardsIn(origin), sa.getParam("ChangeType"), sa); if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(oppType)) < 400) {
// AI gets nothing return false;
final CardCollection aiCards = CardLists.filterControlledBy(cards, ai); }
if (aiCards.isEmpty()) } // otherwise evaluate both lists by CMC and pass only if human
return false; // permanents are less valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
// Human gets nothing .evaluatePermanentList(oppType)) < 6) {
final CardCollection humanCards = CardLists.filterControlledBy(cards, ai.getOpponents()); return false;
if (humanCards.isEmpty()) }
return true; } else {
// don't activate if human gets more back than AI does
// if AI creature is better than Human Creature if ((CardLists.getNotType(oppType, "Creature").size() == 0)
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
.evaluateCreatureList(humanCards)) { if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
return true; .evaluateCreatureList(oppType) + 100)) {
} return false;
return false; }
} } // otherwise evaluate both lists by CMC and pass only if human
return true; // permanents are less valuable
} else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
.evaluatePermanentList(oppType) + 2)) {
@Override return false;
protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) { }
// Change Zone All, can be any type moving from one zone to another }
}
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0); return (((MyRandom.getRandom().nextFloat() < .8) || sa.isTrigger()) && chance);
}
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
// TODO: this is a stub to prevent the AI from crashing the game when, for instance, playing the opponent's /**
// Profaner from exile without paying its mana cost. Otherwise the card is marked RemAIDeck and there is no * <p>
// specific AI to support playing it in a smarter way. Feel free to expand. * changeZoneAllPlayDrawbackAI.
return !CardLists.filter(ai.getOpponents().getCardsIn(origin), CardPredicates.Presets.CREATURES).isEmpty(); * </p>
} * @param sa
* a {@link forge.game.spellability.SpellAbility} object.
CardCollectionView humanType = CardLists.filterControlledBy(ai.getGame().getCardsIn(origin), ai.getOpponents()); * @param aiPlayer
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa); * a {@link forge.game.player.Player} object.
*
CardCollectionView computerType = ai.getCardsIn(origin); * @return a boolean.
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa); */
@Override
// TODO improve restrictions on when the AI would want to use this public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// spBounceAll has some AI we can compare to. // if putting cards from hand to library and parent is drawing cards
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) { // make sure this will actually do something:
if (sa.usesTargeting()) {
// search targetable Opponents return true;
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), }
PlayerPredicates.isTargetableBy(sa));
/* (non-Javadoc)
// get the one with the most handsize * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
Player oppTarget = Collections.max(Lists.newArrayList(oppList), */
PlayerPredicates.compareByZoneSize(origin)); @Override
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// set the target final Card source = sa.getHostCard();
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) { final String hostName = source.getName();
sa.resetTargets(); final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
sa.getTargets().add(oppTarget);
} else { if (hostName.equals("Dawnbreak Reclaimer")) {
return false; final CardCollectionView cards = AbilityUtils.filterListByType(ai.getGame().getCardsIn(origin), sa.getParam("ChangeType"), sa);
}
} // AI gets nothing
} else if (origin.equals(ZoneType.Battlefield)) { final CardCollection aiCards = CardLists.filterControlledBy(cards, ai);
// if mandatory, no need to evaluate if (aiCards.isEmpty())
if (mandatory) { return false;
return true;
} // Human gets nothing
// this statement is assuming the AI is trying to use this spell offensively final CardCollection humanCards = CardLists.filterControlledBy(cards, ai.getOpponents());
// if the AI is using it defensively, then something else needs to occur if (humanCards.isEmpty())
// if only creatures are affected evaluate both lists and pass only return true;
// if human creatures are more valuable
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) { // if AI creature is better than Human Creature
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
.evaluateCreatureList(humanType)) { .evaluateCreatureList(humanCards)) {
return false; return true;
} }
} // otherwise evaluate both lists by CMC and pass only if human return false;
// permanents are more valuable }
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard return true;
.evaluatePermanentList(humanType)) { }
return false;
} @Override
} else if (origin.equals(ZoneType.Graveyard)) { protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) { // Change Zone All, can be any type moving from one zone to another
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
PlayerPredicates.isTargetableBy(sa)); final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
// get the one with the most in graveyard if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
// zone is visible so evaluate which would be hurt the most // TODO: this is a stub to prevent the AI from crashing the game when, for instance, playing the opponent's
Player oppTarget = Collections.max(Lists.newArrayList(oppList), // Profaner from exile without paying its mana cost. Otherwise the card is marked RemAIDeck and there is no
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa)); // specific AI to support playing it in a smarter way. Feel free to expand.
return !CardLists.filter(ai.getOpponents().getCardsIn(origin), CardPredicates.Presets.CREATURES).isEmpty();
// set the target }
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets(); CardCollectionView humanType = CardLists.filterControlledBy(ai.getGame().getCardsIn(origin), ai.getOpponents());
sa.getTargets().add(oppTarget); humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
} else {
return false; CardCollectionView computerType = ai.getCardsIn(origin);
} computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
}
} else if (origin.equals(ZoneType.Exile)) { // TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
} else if (origin.equals(ZoneType.Stack)) { if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
// time stop can do something like this: if (sa.usesTargeting()) {
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip // search targetable Opponents
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
// otherwise, this situation doesn't exist PlayerPredicates.isTargetableBy(sa));
return false;
} // get the one with the most handsize
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
if (destination.equals(ZoneType.Battlefield)) { PlayerPredicates.compareByZoneSize(origin));
// if mandatory, no need to evaluate
if (mandatory) { // set the target
return true; if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
} sa.resetTargets();
if (sa.getParam("GainControl") != null) { sa.getTargets().add(oppTarget);
// Check if the cards are valuable enough } else {
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) { return false;
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard }
.evaluateCreatureList(humanType)) < 1) { }
return false; } else if (origin.equals(ZoneType.Battlefield)) {
} // if mandatory, no need to evaluate
} // otherwise evaluate both lists by CMC and pass only if human if (mandatory) {
// permanents are less valuable return true;
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard }
.evaluatePermanentList(humanType)) < 1) { // this statement is assuming the AI is trying to use this spell offensively
return false; // if the AI is using it defensively, then something else needs to occur
} // if only creatures are affected evaluate both lists and pass only
} else { // if human creatures are more valuable
// don't activate if human gets more back than AI does if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) { if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard .evaluateCreatureList(humanType)) {
.evaluateCreatureList(humanType)) { return false;
return false; }
} } // otherwise evaluate both lists by CMC and pass only if human
} // otherwise evaluate both lists by CMC and pass only if human // permanents are more valuable
// permanents are less valuable else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard .evaluatePermanentList(humanType)) {
.evaluatePermanentList(humanType)) { return false;
return false; }
} } else if (origin.equals(ZoneType.Graveyard)) {
} if (sa.usesTargeting()) {
} // search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
return true; PlayerPredicates.isTargetableBy(sa));
}
// get the one with the most in graveyard
} // zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Exile)) {
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
// otherwise, this situation doesn't exist
return false;
}
if (destination.equals(ZoneType.Battlefield)) {
// if mandatory, no need to evaluate
if (mandatory) {
return true;
}
if (sa.getParam("GainControl") != null) {
// Check if the cards are valuable enough
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(humanType)) < 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(humanType)) < 1) {
return false;
}
} else {
// don't activate if human gets more back than AI does
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
}
}
return true;
}
}

View File

@@ -1,244 +1,238 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import com.google.common.collect.Lists;
import java.util.Random; import forge.ai.*;
import forge.game.ability.effects.CharmEffect;
import com.google.common.collect.Lists; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.ai.AiController; import forge.game.spellability.SpellAbility;
import forge.ai.AiPlayDecision; import forge.util.Aggregates;
import forge.ai.ComputerUtilAbility; import forge.util.MyRandom;
import forge.ai.PlayerControllerAi; import forge.util.collect.FCollection;
import forge.ai.SpellAbilityAi;
import forge.game.ability.effects.CharmEffect; import java.util.List;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub; public class CharmAi extends SpellAbilityAi {
import forge.game.spellability.SpellAbility; @Override
import forge.util.Aggregates; protected boolean checkApiLogic(Player ai, SpellAbility sa) {
import forge.util.MyRandom; // sa is Entwined, no need for extra logic
import forge.util.collect.FCollection; if (sa.isEntwine()) {
return true;
public class CharmAi extends SpellAbilityAi { }
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) { final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
// sa is Entwined, no need for extra logic final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
if (sa.isEntwine()) { boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
return true;
} // Reset the chosen list otherwise it will be locked in forever by earlier calls
sa.setChosenList(null);
final Random r = MyRandom.getRandom(); List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
List<AbilitySub> chosenList;
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num; if (!ai.equals(sa.getActivatingPlayer())) {
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now? // This branch is for "An Opponent chooses" Charm spells from Alliances
// Current just choose the first available spell, which seem generally less disastrous for the AI.
// Reset the chosen list otherwise it will be locked in forever by earlier calls //return choices.subList(0, 1);
sa.setChosenList(null); chosenList = choices.subList(1, choices.size());
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa); } else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) {
List<AbilitySub> chosenList; chosenList = chooseTriskaidekaphobia(choices, ai);
} else {
if (!ai.equals(sa.getActivatingPlayer())) { /*
// This branch is for "An Opponent chooses" Charm spells from Alliances * The generic chooseOptionsAi uses canPlayAi() to determine good choices
// Current just choose the first available spell, which seem generally less disastrous for the AI. * which means most "bonus" effects like life-gain and random pumps will
//return choices.subList(0, 1); * usually not be chosen. This is designed to force the AI to only select
chosenList = choices.subList(1, choices.size()); * the best choice(s) since it does not actually know if it can pay for
} else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) { * "bonus" choices (eg. Entwine/Escalate).
chosenList = chooseTriskaidekaphobia(choices, ai); * chooseMultipleOptionsAi() uses "AILogic$Good" tags to manually identify
} else { * bonus choice(s) for the AI otherwise it might be too hard to ever fulfil
/* * minimum choice requirements with canPlayAi() alone.
* The generic chooseOptionsAi uses canPlayAi() to determine good choices */
* which means most "bonus" effects like life-gain and random pumps will chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min)
* usually not be chosen. This is designed to force the AI to only select : chooseOptionsAi(choices, ai, timingRight, num, min, sa.hasParam("CanRepeatModes"));
* the best choice(s) since it does not actually know if it can pay for }
* "bonus" choices (eg. Entwine/Escalate).
* chooseMultipleOptionsAi() uses "AILogic$Good" tags to manually identify if (chosenList.isEmpty()) {
* bonus choice(s) for the AI otherwise it might be too hard to ever fulfil if (timingRight) {
* minimum choice requirements with canPlayAi() alone. // Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
*/ chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes"));
chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min) } else {
: chooseOptionsAi(choices, ai, timingRight, num, min, sa.hasParam("CanRepeatModes")); return false;
} }
}
if (chosenList.isEmpty()) { sa.setChosenList(chosenList);
if (timingRight) {
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null // prevent run-away activations - first time will always return true
chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes")); return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
} else { }
return false;
} private List<AbilitySub> chooseOptionsAi(List<AbilitySub> choices, final Player ai, boolean isTrigger, int num,
} int min, boolean allowRepeat) {
sa.setChosenList(chosenList); List<AbilitySub> chosenList = Lists.newArrayList();
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
// prevent run-away activations - first time will always return true // First pass using standard canPlayAi() for good choices
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); for (AbilitySub sub : choices) {
} sub.setActivatingPlayer(ai);
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
private List<AbilitySub> chooseOptionsAi(List<AbilitySub> choices, final Player ai, boolean isTrigger, int num, if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
int min, boolean allowRepeat) { chosenList.add(sub);
List<AbilitySub> chosenList = Lists.newArrayList(); if (chosenList.size() == num) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); return chosenList; // maximum choices reached
// First pass using standard canPlayAi() for good choices }
for (AbilitySub sub : choices) { }
sub.setActivatingPlayer(ai); }
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); if (isTrigger && chosenList.size() < min) {
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { // Second pass using doTrigger(false) to fulfil minimum choice
chosenList.add(sub); choices.removeAll(chosenList);
if (chosenList.size() == num) { for (AbilitySub sub : choices) {
return chosenList; // maximum choices reached sub.setActivatingPlayer(ai);
} sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
} if (aic.doTrigger(sub, false)) {
} chosenList.add(sub);
if (isTrigger && chosenList.size() < min) { if (chosenList.size() == min) {
// Second pass using doTrigger(false) to fulfil minimum choice return chosenList;
choices.removeAll(chosenList); }
for (AbilitySub sub : choices) { }
sub.setActivatingPlayer(ai); }
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); // Third pass using doTrigger(true) to force fill minimum choices
if (aic.doTrigger(sub, false)) { if (chosenList.size() < min) {
chosenList.add(sub); choices.removeAll(chosenList);
if (chosenList.size() == min) { for (AbilitySub sub : choices) {
return chosenList; sub.setActivatingPlayer(ai);
} sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
} if (aic.doTrigger(sub, true)) {
} chosenList.add(sub);
// Third pass using doTrigger(true) to force fill minimum choices if (chosenList.size() == min) {
if (chosenList.size() < min) { break;
choices.removeAll(chosenList); }
for (AbilitySub sub : choices) { }
sub.setActivatingPlayer(ai); }
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); }
if (aic.doTrigger(sub, true)) { }
chosenList.add(sub); if (chosenList.size() < min) {
if (chosenList.size() == min) { chosenList.clear(); // not enough choices
break; }
} return chosenList;
} }
}
} private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) {
} List<AbilitySub> chosenList = Lists.newArrayList();
if (chosenList.size() < min) { if (choices == null || choices.isEmpty()) { return chosenList; }
chosenList.clear(); // not enough choices
} AbilitySub gain = choices.get(0);
return chosenList; AbilitySub lose = choices.get(1);
} FCollection<Player> opponents = ai.getOpponents();
private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) { boolean oppTainted = false;
List<AbilitySub> chosenList = Lists.newArrayList(); boolean allyTainted = ai.isCardInPlay("Tainted Remedy");
AbilitySub gain = choices.get(0); final int aiLife = ai.getLife();
AbilitySub lose = choices.get(1);
FCollection<Player> opponents = ai.getOpponents(); //Check if Opponent controls Tainted Remedy
for (Player p : opponents) {
boolean oppTainted = false; if (p.isCardInPlay("Tainted Remedy")) {
boolean allyTainted = ai.isCardInPlay("Tainted Remedy"); oppTainted = true;
final int aiLife = ai.getLife(); break;
}
//Check if Opponent controls Tainted Remedy }
for (Player p : opponents) { // if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose
if (p.isCardInPlay("Tainted Remedy")) { if (!allyTainted) {
oppTainted = true; for (Player p : ai.getAllies()) {
break; if (p.isCardInPlay("Tainted Remedy")) {
} allyTainted = true;
} break;
// if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose }
if (!allyTainted) { }
for (Player p : ai.getAllies()) { }
if (p.isCardInPlay("Tainted Remedy")) {
allyTainted = true; if (!ai.canLoseLife() || ai.cantLose()) {
break; // ai cant lose life, or cant lose the game, don't think about others
} chosenList.add(allyTainted ? gain : lose);
} } else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
} // Rain of Gore does negate lifegain, so don't benefit the others
// same for if a oppoent does control Tainted Remedy
if (!ai.canLoseLife() || ai.cantLose()) { // but if ai cant gain life, the effects are negated
// ai cant lose life, or cant lose the game, don't think about others chosenList.add(ai.canGainLife() ? lose : gain);
chosenList.add(allyTainted ? gain : lose); } else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) { // no life gain, but extra life loss.
// Rain of Gore does negate lifegain, so don't benefit the others if (aiLife >= 17)
// same for if a oppoent does control Tainted Remedy chosenList.add(lose);
// but if ai cant gain life, the effects are negated // try to prevent to get to 13 with extra lose
chosenList.add(ai.canGainLife() ? lose : gain); else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) { chosenList.add(gain);
// no life gain, but extra life loss. } else {
if (aiLife >= 17) chosenList.add(lose);
chosenList.add(lose); }
// try to prevent to get to 13 with extra lose } else if (ai.canGainLife() && aiLife <= 5) {
else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) { // critical Life try to gain more
chosenList.add(gain); chosenList.add(gain);
} else { } else if(!ai.canGainLife() && aiLife == 14 ) {
chosenList.add(lose); // ai cant gain life, but try to avoid falling to 13
} // but if a oppoent does control Tainted Remedy its irrelevant
} else if (ai.canGainLife() && aiLife <= 5) { chosenList.add(oppTainted ? lose : gain);
// critical Life try to gain more } else if (allyTainted) {
chosenList.add(gain); // Tainted Remedy negation logic, try gain instead of lose
} else if(!ai.canGainLife() && aiLife == 14 ) { // because negation does turn it into lose for opponents
// ai cant gain life, but try to avoid falling to 13 boolean oppCritical = false;
// but if a oppoent does control Tainted Remedy its irrelevant // an oppoent is Critical = 14, and can't gain life, try to lose life instead
chosenList.add(oppTainted ? lose : gain); // but only if ai doesn't kill itself with that.
} else if (allyTainted) { if (aiLife != 14) {
// Tainted Remedy negation logic, try gain instead of lose for (Player p : opponents) {
// because negation does turn it into lose for opponents if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) {
boolean oppCritical = false; oppCritical = true;
// an oppoent is Critical = 14, and can't gain life, try to lose life instead break;
// but only if ai doesn't kill itself with that. }
if (aiLife != 14) { }
for (Player p : opponents) { }
if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) { chosenList.add(aiLife == 12 || oppCritical ? lose : gain);
oppCritical = true; } else {
break; // normal logic, try to gain life if its critical
} boolean oppCritical = false;
} // an oppoent is Critical = 12, and can gain life, try to gain life instead
} // but only if ai doesn't kill itself with that.
chosenList.add(aiLife == 12 || oppCritical ? lose : gain); if (aiLife != 12) {
} else { for (Player p : opponents) {
// normal logic, try to gain life if its critical if (p.getLife() == 12 && p.canGainLife()) {
boolean oppCritical = false; oppCritical = true;
// an oppoent is Critical = 12, and can gain life, try to gain life instead break;
// but only if ai doesn't kill itself with that. }
if (aiLife != 12) { }
for (Player p : opponents) { }
if (p.getLife() == 12 && p.canGainLife()) { chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose);
oppCritical = true; }
break; return chosenList;
} }
}
} // Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands)
chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose); private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) {
} AbilitySub goodChoice = null;
return chosenList; List<AbilitySub> chosenList = Lists.newArrayList();
} AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
for (AbilitySub sub : choices) {
// Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands) sub.setActivatingPlayer(ai);
private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) { // Assign generic good choice to fill up choices if necessary
AbilitySub goodChoice = null; if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) {
List<AbilitySub> chosenList = Lists.newArrayList(); goodChoice = sub;
AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); } else {
for (AbilitySub sub : choices) { // Standard canPlayAi()
sub.setActivatingPlayer(ai); if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
// Assign generic good choice to fill up choices if necessary chosenList.add(sub);
if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) { if (chosenList.size() == min) {
goodChoice = sub; break; // enough choices
} else { }
// Standard canPlayAi() }
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { }
chosenList.add(sub); }
if (chosenList.size() == min) { // Add generic good choice if one more choice is needed
break; // enough choices if (chosenList.size() == min - 1 && goodChoice != null) {
} chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
} }
} if (chosenList.size() != min) {
} chosenList.clear();
// Add generic good choice if one more choice is needed }
if (chosenList.size() == min - 1 && goodChoice != null) { return chosenList;
chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work }
}
if (chosenList.size() != min) { @Override
chosenList.clear(); public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
} return Aggregates.random(opponents);
return chosenList; }
} }
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
return Aggregates.random(opponents);
}
}

View File

@@ -1,267 +1,282 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtilCard;
import forge.game.Game; import forge.ai.ComputerUtilCombat;
import forge.game.card.Card; import forge.ai.SpellAbilityAi;
import forge.game.card.CardCollection; import forge.game.Game;
import forge.game.card.CardCollectionView; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardCollection;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardCollectionView;
import forge.game.card.CounterType; import forge.game.card.CardLists;
import forge.game.combat.Combat; import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType; import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player; import forge.game.card.CounterType;
import forge.game.player.PlayerPredicates; import forge.game.combat.Combat;
import forge.game.spellability.SpellAbility; import forge.game.keyword.Keyword;
import forge.game.zone.ZoneType; import forge.game.phase.PhaseType;
import forge.util.Aggregates; import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
public class ChooseCardAi extends SpellAbilityAi { import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/** import forge.util.Aggregates;
* The rest of the logic not covered by the canPlayAI template is defined here
*/ public class ChooseCardAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { /**
if (sa.usesTargeting()) { * The rest of the logic not covered by the canPlayAI template is defined here
sa.resetTargets(); */
// search targetable Opponents @Override
final List<Player> oppList = Lists.newArrayList(Iterables.filter( protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
ai.getOpponents(), PlayerPredicates.isTargetableBy(sa))); if (sa.usesTargeting()) {
sa.resetTargets();
if (oppList.isEmpty()) { // search targetable Opponents
return false; final List<Player> oppList = Lists.newArrayList(Iterables.filter(
} ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)));
sa.getTargets().add(Iterables.getFirst(oppList, null)); if (oppList.isEmpty()) {
} return false;
return true; }
}
sa.getTargets().add(Iterables.getFirst(oppList, null));
/** }
* Checks if the AI will play a SpellAbility with the specified AiLogic return true;
*/ }
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { /**
final Card host = sa.getHostCard(); * Checks if the AI will play a SpellAbility with the specified AiLogic
final Game game = ai.getGame(); */
ZoneType choiceZone = ZoneType.Battlefield; @Override
if (sa.hasParam("ChoiceZone")) { protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone")); final Card host = sa.getHostCard();
} final Game game = ai.getGame();
CardCollectionView choices = ai.getGame().getCardsIn(choiceZone); ZoneType choiceZone = ZoneType.Battlefield;
if (sa.hasParam("Choices")) { if (sa.hasParam("ChoiceZone")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host); choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
} }
if (sa.hasParam("TargetControls")) { CardCollectionView choices = ai.getGame().getCardsIn(choiceZone);
choices = CardLists.filterControlledBy(choices, ai.getOpponents()); if (sa.hasParam("Choices")) {
} choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) { }
if (choices.isEmpty()) { if (sa.hasParam("TargetControls")) {
return false; choices = CardLists.filterControlledBy(choices, ai.getOpponents());
} }
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) { if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
if (choices.size() < 2) { if (choices.isEmpty()) {
return false; return false;
} }
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) { } else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" if (choices.size() < 2) {
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva"; return false;
}
choices = CardLists.getValidCards(choices, filter, host.getController(), host); } else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
if (choices.isEmpty()) { final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
return false; : "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
}
} else if (aiLogic.equals("Never")) { choices = CardLists.getValidCards(choices, filter, host.getController(), host);
return false; if (choices.isEmpty()) {
} else if (aiLogic.equals("NeedsPrevention")) { return false;
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { }
return false; } else if (aiLogic.equals("Never")) {
} return false;
final Combat combat = game.getCombat(); } else if (aiLogic.equals("NeedsPrevention")) {
choices = CardLists.filter(choices, new Predicate<Card>() { if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
@Override return false;
public boolean apply(final Card c) { }
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { final Combat combat = game.getCombat();
return false; choices = CardLists.filter(choices, new Predicate<Card>() {
} @Override
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0; public boolean apply(final Card c) {
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref; if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
} return false;
}); }
if (choices.isEmpty()) { int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return false; return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
} }
} else if (aiLogic.equals("Ashiok")) { });
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1; if (choices.isEmpty()) {
for (int i = loyalty; i >= 0; i--) { return false;
host.setSVar("ChosenX", "Number$" + i); }
choices = ai.getGame().getCardsIn(choiceZone); } else if (aiLogic.equals("Ashiok")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host); final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
if (!choices.isEmpty()) { for (int i = loyalty; i >= 0; i--) {
return true; host.setSVar("ChosenX", "Number$" + i);
} choices = ai.getGame().getCardsIn(choiceZone);
} choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
if (!choices.isEmpty()) {
if (choices.isEmpty()) { return true;
return false; }
} }
} else if (aiLogic.equals("RandomNonLand")) {
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) { if (choices.isEmpty()) {
return false; return false;
} }
} else if (aiLogic.equals("Duneblast")) { } else if (aiLogic.equals("RandomNonLand")) {
CardCollection aiCreatures = ai.getCreaturesInPlay(); if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
CardCollection oppCreatures = ai.getOpponent().getCreaturesInPlay(); return false;
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible"); }
oppCreatures = CardLists.getNotKeyword(oppCreatures, "Indestructible"); } else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay();
// Use it as a wrath, when the human creatures threat the ai's life CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) { aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
return true; oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
}
// Use it as a wrath, when the human creatures threat the ai's life
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures); if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) {
aiCreatures.remove(chosen); return true;
int minGain = 200; }
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
.evaluateCreatureList(oppCreatures)) { aiCreatures.remove(chosen);
return false; int minGain = 200;
}
} if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
return true; .evaluateCreatureList(oppCreatures)) {
} return false;
}
@Override }
public boolean chkAIDrawback(SpellAbility sa, Player ai) { return true;
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) { }
return false;
} @Override
return checkApiLogic(ai, sa); public boolean chkAIDrawback(SpellAbility sa, Player ai) {
} if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
return false;
/* (non-Javadoc) }
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean) return checkApiLogic(ai, sa);
*/ }
@Override
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { /* (non-Javadoc)
final Card host = sa.getHostCard(); * @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
final Player ctrl = host.getController(); */
final String logic = sa.getParam("AILogic"); @Override
Card choice = null; public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
if (logic == null) { final Card host = sa.getHostCard();
// Base Logic is choose "best" final Player ctrl = host.getController();
choice = ComputerUtilCard.getBestAI(options); final String logic = sa.getParam("AILogic");
} else if ("WorstCard".equals(logic)) { Card choice = null;
choice = ComputerUtilCard.getWorstAI(options); if (logic == null) {
} else if (logic.equals("BestBlocker")) { // Base Logic is choose "best"
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) { choice = ComputerUtilCard.getBestAI(options);
options = CardLists.filter(options, Presets.UNTAPPED); } else if ("WorstCard".equals(logic)) {
} choice = ComputerUtilCard.getWorstAI(options);
choice = ComputerUtilCard.getBestCreatureAI(options); } else if (logic.equals("BestBlocker")) {
} else if (logic.equals("Clone") || logic.equals("Vesuva")) { if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" : options = CardLists.filter(options, Presets.UNTAPPED);
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva"; }
choice = ComputerUtilCard.getBestCreatureAI(options);
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); } else if (logic.equals("Clone") || logic.equals("Vesuva")) {
if (!newOptions.isEmpty()) { final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
options = newOptions; "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
}
choice = ComputerUtilCard.getBestAI(options); CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) { if (!newOptions.isEmpty()) {
choice = null; options = newOptions;
} }
} else if ("RandomNonLand".equals(logic)) { choice = ComputerUtilCard.getBestAI(options);
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host); if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
choice = Aggregates.random(options); choice = null;
} else if (logic.equals("Untap")) { }
final String filter = "Permanent.YouCtrl,Permanent.tapped"; } else if ("RandomNonLand".equals(logic)) {
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host);
if (!newOptions.isEmpty()) { choice = Aggregates.random(options);
options = newOptions; } else if (logic.equals("Untap")) {
} final String filter = "Permanent.YouCtrl,Permanent.tapped";
choice = ComputerUtilCard.getBestAI(options); CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
} else if (logic.equals("NeedsPrevention")) { if (!newOptions.isEmpty()) {
final Game game = ai.getGame(); options = newOptions;
final Combat combat = game.getCombat(); }
CardCollectionView better = CardLists.filter(options, new Predicate<Card>() { choice = ComputerUtilCard.getBestAI(options);
@Override } else if (logic.equals("NeedsPrevention")) {
public boolean apply(final Card c) { final Game game = ai.getGame();
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { final Combat combat = game.getCombat();
return false; CardCollectionView better = CardLists.filter(options, new Predicate<Card>() {
} @Override
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0; public boolean apply(final Card c) {
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref; if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
} return false;
}); }
if (!better.isEmpty()) { int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
choice = ComputerUtilCard.getBestAI(better); return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
} else { }
choice = ComputerUtilCard.getBestAI(options); });
} if (!better.isEmpty()) {
} else if ("OppPreferred".equals(logic)) { choice = ComputerUtilCard.getBestAI(better);
CardCollectionView oppControlled = CardLists.filterControlledBy(options, ai.getOpponents()); } else {
if (!oppControlled.isEmpty()) { choice = ComputerUtilCard.getBestAI(options);
choice = ComputerUtilCard.getBestAI(oppControlled); }
} else { } else if ("OppPreferred".equals(logic)) {
CardCollectionView aiControlled = CardLists.filterControlledBy(options, ai); CardCollectionView oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
choice = ComputerUtilCard.getWorstAI(aiControlled); if (!oppControlled.isEmpty()) {
} choice = ComputerUtilCard.getBestAI(oppControlled);
} else if ("LowestCMCCreature".equals(logic)) { } else {
CardCollection creats = CardLists.filter(options, Presets.CREATURES); CardCollectionView aiControlled = CardLists.filterControlledBy(options, ai);
creats = CardLists.filterToughness(creats, 1); choice = ComputerUtilCard.getWorstAI(aiControlled);
if (creats.isEmpty()) { }
choice = ComputerUtilCard.getWorstAI(options); } else if ("LowestCMCCreature".equals(logic)) {
} else { CardCollection creats = CardLists.filter(options, Presets.CREATURES);
CardLists.sortByCmcDesc(creats); creats = CardLists.filterToughness(creats, 1);
Collections.reverse(creats); if (creats.isEmpty()) {
choice = creats.get(0); choice = ComputerUtilCard.getWorstAI(options);
} } else {
} else if ("TangleWire".equals(logic)) { CardLists.sortByCmcDesc(creats);
CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() { Collections.reverse(creats);
@Override choice = creats.get(0);
public boolean apply(final Card c) { }
if (c.isCreature()) { } else if ("NegativePowerFirst".equals(logic)) {
return false; Card lowest = Aggregates.itemWithMin(options, CardPredicates.Accessors.fnGetNetPower);
} if (lowest.getNetPower() <= 0) {
for (SpellAbility sa : c.getAllSpellAbilities()) { choice = lowest;
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) { } else {
return false; choice = ComputerUtilCard.getBestCreatureAI(options);
} }
} } else if ("TangleWire".equals(logic)) {
return true; CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() {
} @Override
}); public boolean apply(final Card c) {
System.out.println("Tangle Wire" + options + " - " + betterList); if (c.isCreature()) {
if (!betterList.isEmpty()) { return false;
choice = betterList.get(0); }
} else { for (SpellAbility sa : c.getAllSpellAbilities()) {
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false); if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
} return false;
} else if (logic.equals("Duneblast")) { }
CardCollectionView aiCreatures = ai.getCreaturesInPlay(); }
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible"); return true;
}
if (aiCreatures.isEmpty()) { });
return null; System.out.println("Tangle Wire" + options + " - " + betterList);
} if (!betterList.isEmpty()) {
choice = betterList.get(0);
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures); } else {
return chosen; choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
} else { }
choice = ComputerUtilCard.getBestAI(options); } else if (logic.equals("Duneblast")) {
} CardCollectionView aiCreatures = ai.getCreaturesInPlay();
return choice; aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
}
} if (aiCreatures.isEmpty()) {
return null;
}
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
return chosen;
} else if (logic.equals("OrzhovAdvokist")) {
if (ai.equals(sa.getActivatingPlayer())) {
choice = ComputerUtilCard.getBestAI(options);
} // TODO: improve ai
} else {
choice = ComputerUtilCard.getBestAI(options);
}
return choice;
}
}

View File

@@ -1,122 +1,106 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.StaticData; import forge.StaticData;
import forge.ai.ComputerUtil; import forge.ai.*;
import forge.ai.ComputerUtilCard; import forge.card.CardDb;
import forge.ai.ComputerUtilMana; import forge.card.CardRules;
import forge.ai.SpellAbilityAi; import forge.card.CardSplitType;
import forge.card.CardDb; import forge.card.CardStateName;
import forge.card.CardRules; import forge.card.ICardFace;
import forge.card.CardSplitType; import forge.game.card.Card;
import forge.card.CardStateName; import forge.game.card.CardUtil;
import forge.card.ICardFace; import forge.game.player.Player;
import forge.game.card.Card; import forge.game.spellability.SpellAbility;
import forge.game.card.CardUtil; import forge.game.spellability.TargetRestrictions;
import forge.game.phase.PhaseType; import forge.item.PaperCard;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility; public class ChooseCardNameAi extends SpellAbilityAi {
import forge.game.spellability.TargetRestrictions;
import forge.item.PaperCard; @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
public class ChooseCardNameAi extends SpellAbilityAi { Card source = sa.getHostCard();
if (sa.hasParam("AILogic")) {
@Override // Don't tap creatures that may be able to block
protected boolean canPlayAI(Player ai, SpellAbility sa) { if (ComputerUtil.waitForBlocking(sa)) {
Card source = sa.getHostCard(); return false;
if (sa.hasParam("AILogic")) { }
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) { String logic = sa.getParam("AILogic");
return false; if (logic.equals("MomirAvatar")) {
} return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
} else if (logic.equals("CursedScroll")) {
String logic = sa.getParam("AILogic"); return SpecialCardAi.CursedScroll.consider(ai, sa);
if (logic.equals("MomirAvatar")) { }
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
return false; final TargetRestrictions tgt = sa.getTargetRestrictions();
} if (tgt != null) {
// Set PayX here to maximum value. sa.resetTargets();
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai); if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
// Some basic strategy for Momir } else {
if (tokenSize < 2) { sa.getTargets().add(ai);
return false; }
} }
return true;
if (tokenSize > 11) { }
tokenSize = 11; return false;
} }
source.setSVar("PayX", Integer.toString(tokenSize)); @Override
} protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
// TODO - there is no AILogic implemented yet
final TargetRestrictions tgt = sa.getTargetRestrictions(); return false;
if (tgt != null) { }
sa.resetTargets(); /* (non-Javadoc)
if (tgt.canOnlyTgtOpponent()) { * @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
sa.getTargets().add(ai.getOpponent()); */
} else { @Override
sa.getTargets().add(ai); public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
}
} return ComputerUtilCard.getBestAI(options);
return true; }
}
return false; @Override
} public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
// this function is only for "Alhammarret, High Arbiter"
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { if (faces.isEmpty()) {
// TODO - there is no AILogic implemented yet return "";
return false; } else if (faces.size() == 1) {
} return Iterables.getFirst(faces, null).getName();
/* (non-Javadoc) }
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/ List<Card> cards = Lists.newArrayList();
@Override final CardDb cardDb = StaticData.instance().getCommonCards();
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestAI(options); for (ICardFace face : faces) {
} final CardRules rules = cardDb.getRules(face.getName());
boolean isOther = rules.getOtherPart() == face;
@Override final PaperCard paper = cardDb.getCard(rules.getName());
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) { final Card card = Card.fromPaperCard(paper, ai);
// this function is only for "Alhammarret, High Arbiter"
if (rules.getSplitType() == CardSplitType.Split) {
if (faces.isEmpty()) { Card copy = CardUtil.getLKICopy(card);
return ""; // for calcing i need only one split side
} else if (faces.size() == 1) { if (isOther) {
return Iterables.getFirst(faces, null).getName(); copy.getCurrentState().copyFrom(card.getState(CardStateName.RightSplit), true);
} } else {
copy.getCurrentState().copyFrom(card.getState(CardStateName.LeftSplit), true);
List<Card> cards = Lists.newArrayList(); }
final CardDb cardDb = StaticData.instance().getCommonCards(); copy.updateStateForView();
for (ICardFace face : faces) { cards.add(copy);
final CardRules rules = cardDb.getRules(face.getName()); } else if (!isOther) {
boolean isOther = rules.getOtherPart() == face; // other can't be cast that way, not need to prevent that
final PaperCard paper = cardDb.getCard(rules.getName()); cards.add(card);
final Card card = Card.fromPaperCard(paper, ai); }
}
if (rules.getSplitType() == CardSplitType.Split) {
Card copy = CardUtil.getLKICopy(card); return ComputerUtilCard.getBestAI(cards).getName();
// for calcing i need only one split side }
if (isOther) { }
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.RightSplit));
} else {
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.LeftSplit));
}
copy.updateStateForView();
cards.add(copy);
} else if (!isOther) {
// other can't be cast that way, not need to prevent that
cards.add(card);
}
}
return ComputerUtilCard.getBestAI(cards).getName();
}
}

View File

@@ -1,98 +1,97 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicates; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilMana;
import forge.ai.ComputerUtilMana; import forge.ai.SpecialCardAi;
import forge.ai.SpecialCardAi; import forge.ai.SpellAbilityAi;
import forge.ai.SpellAbilityAi; import forge.card.MagicColor;
import forge.card.MagicColor; import forge.game.Game;
import forge.game.Game; import forge.game.card.Card;
import forge.game.card.Card; import forge.game.card.CardCollectionView;
import forge.game.card.CardCollectionView; import forge.game.card.CardLists;
import forge.game.card.CardLists; import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType;
import forge.game.phase.PhaseType; import forge.game.player.Player;
import forge.game.player.Player; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType;
import forge.game.zone.ZoneType; import forge.util.MyRandom;
import forge.util.MyRandom;
public class ChooseColorAi extends SpellAbilityAi {
public class ChooseColorAi extends SpellAbilityAi {
@Override
@Override protected boolean canPlayAI(Player ai, SpellAbility sa) {
protected boolean canPlayAI(Player ai, SpellAbility sa) { final Card source = sa.getHostCard();
final Card source = sa.getHostCard(); final Game game = ai.getGame();
final Game game = ai.getGame(); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final PhaseHandler ph = game.getPhaseHandler();
final PhaseHandler ph = game.getPhaseHandler();
if (!sa.hasParam("AILogic")) {
if (!sa.hasParam("AILogic")) { return false;
return false; }
} final String logic = sa.getParam("AILogic");
final String logic = sa.getParam("AILogic");
if (ComputerUtil.preventRunAwayActivations(sa)) {
if (ComputerUtil.preventRunAwayActivations(sa)) { return false;
return false; }
}
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
if ("Nykthos, Shrine to Nyx".equals(sourceName)) { return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa); }
}
if ("Oona, Queen of the Fae".equals(sourceName)) {
if ("Oona, Queen of the Fae".equals(sourceName)) { if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { return false;
return false; }
} // Set PayX here to maximum value.
// Set PayX here to maximum value. int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
int x = ComputerUtilMana.determineLeftoverMana(sa, ai); source.setSVar("PayX", Integer.toString(x));
source.setSVar("PayX", Integer.toString(x)); return true;
return true; }
}
if ("Addle".equals(sourceName)) {
if ("Addle".equals(sourceName)) { if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).isEmpty()) {
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ai.getOpponent().getCardsIn(ZoneType.Hand).isEmpty()) { return false;
return false; }
} return true;
return true; }
}
if (logic.equals("MostExcessOpponentControls")) {
if (logic.equals("MostExcessOpponentControls")) { for (byte color : MagicColor.WUBRG) {
for (byte color : MagicColor.WUBRG) { CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield); CardCollectionView opplist = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
CardCollectionView opplist = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
ailist = CardLists.filter(ailist, CardPredicates.isColor(color)); opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
int excess = ComputerUtilCard.evaluatePermanentList(opplist) - ComputerUtilCard.evaluatePermanentList(ailist);
int excess = ComputerUtilCard.evaluatePermanentList(opplist) - ComputerUtilCard.evaluatePermanentList(ailist); if (excess > 4) {
if (excess > 4) { return true;
return true; }
} }
} return false;
return false; }
}
if (logic.equals("MostProminentInComputerDeck")) {
if (logic.equals("MostProminentInComputerDeck")) { if ("Astral Cornucopia".equals(sourceName)) {
if ("Astral Cornucopia".equals(sourceName)) { // activate in Main 2 hoping that the extra mana surplus will make a difference
// activate in Main 2 hoping that the extra mana surplus will make a difference // if there are some nonland permanents in hand
// if there are some nonland permanents in hand CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.NONLAND_PERMANENTS);
CardPredicates.Presets.NONLAND_PERMANENTS);
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai); }
} }
}
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); return chance;
return chance; }
}
@Override
@Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { return mandatory || canPlayAI(ai, sa);
return mandatory || canPlayAI(ai, sa); }
}
}
}

View File

@@ -1,32 +1,32 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class ChooseDirectionAi extends SpellAbilityAi { public class ChooseDirectionAi extends SpellAbilityAi {
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final String logic = sa.getParam("AILogic"); final String logic = sa.getParam("AILogic");
if (logic == null) { if (logic == null) {
return false; return false;
} else { } else {
// TODO: default ai // TODO: default ai
} }
return true; return true;
} }
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa); return canPlayAI(ai, sa);
} }
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa); return canPlayAI(ai, sa);
} }
} }

View File

@@ -1,342 +1,350 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.base.Predicate;
import com.google.common.collect.Lists; import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilAbility; import com.google.common.collect.Lists;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtilCost;
import forge.ai.SpellApiToAi; import forge.ai.SpellAbilityAi;
import forge.card.MagicColor; import forge.ai.SpellApiToAi;
import forge.game.Game; import forge.card.MagicColor;
import forge.game.card.Card; import forge.game.Game;
import forge.game.card.CardCollection; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardCollectionView;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardLists;
import forge.game.card.CardUtil; import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType; import forge.game.card.CardUtil;
import forge.game.combat.Combat; import forge.game.card.CounterType;
import forge.game.combat.CombatUtil; import forge.game.combat.Combat;
import forge.game.cost.Cost; import forge.game.combat.CombatUtil;
import forge.game.player.Player; import forge.game.cost.Cost;
import forge.game.spellability.AbilitySub; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.AbilitySub;
import forge.game.zone.ZoneType; import forge.game.spellability.SpellAbility;
import forge.util.Aggregates; import forge.game.zone.ZoneType;
import forge.util.collect.FCollection; import forge.util.Aggregates;
import forge.util.collect.FCollection;
public class ChooseGenericEffectAi extends SpellAbilityAi {
public class ChooseGenericEffectAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { @Override
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) { protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
return true; if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
} else if (aiLogic.startsWith("Fabricate")) { return true;
return true; } else if (aiLogic.startsWith("Fabricate")) {
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) { return true;
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) { } else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) { for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
return true; if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
} return true;
} }
} }
return false; }
} return false;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { @Override
return sa.hasParam("AILogic"); protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
} return sa.hasParam("AILogic");
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player) /* (non-Javadoc)
*/ * @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
@Override */
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { @Override
return canPlayAI(aiPlayer, sa); public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
} return canPlayAI(aiPlayer, sa);
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) { @Override
if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) { protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
for (final Player p : aiPlayer.getOpponents()) { if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) {
if (p.canBeTargetedBy(sa)) { for (final Player p : aiPlayer.getOpponents()) {
sa.resetTargets(); if (p.canBeTargetedBy(sa)) {
sa.getTargets().add(p); sa.resetTargets();
return true; sa.getTargets().add(p);
} return true;
} }
return true; // perhaps the opponent(s) had Sigarda, Heron's Grace or another effect giving hexproof in play, still play the creature as 6/6 }
} return true; // perhaps the opponent(s) had Sigarda, Heron's Grace or another effect giving hexproof in play, still play the creature as 6/6
}
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
} return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) { @Override
Card host = sa.getHostCard(); public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); Map<String, Object> params) {
final Game game = host.getGame(); Card host = sa.getHostCard();
final Combat combat = game.getCombat(); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final String logic = sa.getParam("AILogic"); final Game game = host.getGame();
if (logic == null) { final Combat combat = game.getCombat();
return spells.get(0); final String logic = sa.getParam("AILogic");
} else if ("Random".equals(logic)) { if (logic == null) {
return Aggregates.random(spells); return spells.get(0);
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive } else if ("Random".equals(logic)) {
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() { return Aggregates.random(spells);
@Override } else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
public boolean apply(final SpellAbility sp) { List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land"); @Override
} public boolean apply(final SpellAbility sp) {
})); return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
return Aggregates.random(filtered); }
} else if ("PayUnlessCost".equals(logic)) { }));
for (final SpellAbility sp : spells) { return Aggregates.random(filtered);
String unlessCost = sp.getParam("UnlessCost"); } else if ("PayUnlessCost".equals(logic)) {
sp.setActivatingPlayer(sa.getActivatingPlayer()); for (final SpellAbility sp : spells) {
Cost unless = new Cost(unlessCost, false); String unlessCost = sp.getParam("UnlessCost");
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player); sp.setActivatingPlayer(sa.getActivatingPlayer());
paycost.setPayCosts(unless); Cost unless = new Cost(unlessCost, false);
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<Player>(player)) SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
&& ComputerUtilCost.canPayCost(paycost, player)) { paycost.setPayCosts(unless);
return sp; if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<Player>(player))
} && ComputerUtilCost.canPayCost(paycost, player)) {
} return sp;
return spells.get(0); }
} else if ("Khans".equals(logic) || "Dragons".equals(logic)) { // Fate Reforged sieges }
for (final SpellAbility sp : spells) { return spells.get(0);
if (sp.getDescription().equals(logic)) { } else if ("Khans".equals(logic) || "Dragons".equals(logic)) { // Fate Reforged sieges
return sp; for (final SpellAbility sp : spells) {
} if (sp.getDescription().equals(logic)) {
} return sp;
} else if ("SelfOthers".equals(logic)) { }
SpellAbility self = null, others = null; }
for (final SpellAbility sp : spells) { } else if ("SelfOthers".equals(logic)) {
if (sp.getDescription().equals("Self")) { SpellAbility self = null, others = null;
self = sp; for (final SpellAbility sp : spells) {
} else { if (sp.getDescription().equals("Self")) {
others = sp; self = sp;
} } else {
} others = sp;
String hostname = host.getName(); }
if (hostname.equals("May Civilization Collapse")) { }
if (player.getLandsInPlay().isEmpty()) { String hostname = host.getName();
return self; if (hostname.equals("May Civilization Collapse")) {
} if (player.getLandsInPlay().isEmpty()) {
} else if (hostname.equals("Feed the Machine")) { return self;
if (player.getCreaturesInPlay().isEmpty()) { }
return self; } else if (hostname.equals("Feed the Machine")) {
} if (player.getCreaturesInPlay().isEmpty()) {
} else if (hostname.equals("Surrender Your Thoughts")) { return self;
if (player.getCardsIn(ZoneType.Hand).isEmpty()) { }
return self; } else if (hostname.equals("Surrender Your Thoughts")) {
} if (player.getCardsIn(ZoneType.Hand).isEmpty()) {
} else if (hostname.equals("The Fate of the Flammable")) { return self;
if (!player.canLoseLife()) { }
return self; } else if (hostname.equals("The Fate of the Flammable")) {
} if (!player.canLoseLife()) {
} return self;
return others; }
} else if ("Fatespinner".equals(logic)) { }
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null; return others;
for (final SpellAbility sp : spells) { } else if ("Fatespinner".equals(logic)) {
if (sp.getDescription().equals("FatespinnerSkipDraw")) { SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
skipDraw = sp; for (final SpellAbility sp : spells) {
} else if (sp.getDescription().equals("FatespinnerSkipMain")) { if (sp.getDescription().equals("FatespinnerSkipDraw")) {
//skipMain = sp; skipDraw = sp;
} else { } else if (sp.getDescription().equals("FatespinnerSkipMain")) {
skipCombat = sp; //skipMain = sp;
} } else {
} skipCombat = sp;
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat }
if (player.hasKeyword("Skip your draw step.")) { }
return skipDraw; // FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
} if (player.hasKeyword("Skip your draw step.")) {
if (player.hasKeyword("Skip your next combat phase.")) { return skipDraw;
return skipCombat; }
} if (player.hasKeyword("Skip your next combat phase.")) {
return skipCombat;
// TODO If combat is poor, Skip Combat }
// Todo if hand is empty or mostly empty, skip main phase
// Todo if hand has gas, skip draw // TODO If combat is poor, Skip Combat
return Aggregates.random(spells); // Todo if hand is empty or mostly empty, skip main phase
// Todo if hand has gas, skip draw
} else if ("SinProdder".equals(logic)) { return Aggregates.random(spells);
SpellAbility allow = null, deny = null;
for (final SpellAbility sp : spells) { } else if ("SinProdder".equals(logic)) {
if (sp.getDescription().equals("Allow")) { SpellAbility allow = null, deny = null;
allow = sp; for (final SpellAbility sp : spells) {
} else { if (sp.getDescription().equals("Allow")) {
deny = sp; allow = sp;
} } else {
} deny = sp;
}
Card imprinted = host.getImprintedCards().getFirst(); }
int dmg = imprinted.getCMC();
Player owner = imprinted.getOwner(); Card imprinted = host.getImprintedCards().getFirst();
int dmg = imprinted.getCMC();
//useless cards in hand Player owner = imprinted.getOwner();
if (imprinted.getName().equals("Bridge from Below") ||
imprinted.getName().equals("Haakon, Stromgald Scourge")) { //useless cards in hand
return allow; if (imprinted.getName().equals("Bridge from Below") ||
} imprinted.getName().equals("Haakon, Stromgald Scourge")) {
return allow;
//bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that }
if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
imprinted.getName().equals("Gaea's Blessing") || //bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that
imprinted.getName().equals("Narcomoeba"))) { if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
return allow; imprinted.getName().equals("Gaea's Blessing") ||
} imprinted.getName().equals("Narcomoeba"))) {
return allow;
// milling against Tamiyo is pointless }
if (owner.isCardInCommand("Tamiyo, the Moon Sage emblem")) {
return allow; // milling against Tamiyo is pointless
} if (owner.isCardInCommand("Emblem - Tamiyo, the Moon Sage")) {
return allow;
// milling a land against Gitrog result in card draw }
if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
// try to mill owner // milling a land against Gitrog result in card draw
if (owner.getCardsIn(ZoneType.Library).size() < 5) { if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
return deny; // try to mill owner
} if (owner.getCardsIn(ZoneType.Library).size() < 5) {
return allow; return deny;
} }
return allow;
// milling a creature against Sidisi result in more creatures }
if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
return allow; // milling a creature against Sidisi result in more creatures
} if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
return allow;
//if Iona does prevent from casting, allow it to draw }
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) { //if Iona does prevent from casting, allow it to draw
return allow; for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
} if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
} return allow;
}
if (dmg == 0) { }
// If CMC = 0, mill it!
return deny; if (dmg == 0) {
} else if (dmg + 3 > player.getLife()) { // If CMC = 0, mill it!
// if low on life, do nothing. return deny;
return allow; } else if (dmg + 3 > player.getLife()) {
} else if (player.getLife() - dmg > 15) { // if low on life, do nothing.
// TODO Check "danger" level of card return allow;
// If lots of life, and card might be dangerous? Mill it! } else if (player.getLife() - dmg > 15) {
return deny; // TODO Check "danger" level of card
} // If lots of life, and card might be dangerous? Mill it!
// if unsure, random? return deny;
return Aggregates.random(spells); }
} else if (logic.startsWith("Fabricate")) { // if unsure, random?
final int n = Integer.valueOf(logic.substring("Fabricate".length())); return Aggregates.random(spells);
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1); } else if (logic.startsWith("Fabricate")) {
final int n = Integer.valueOf(logic.substring("Fabricate".length()));
// check for something which might prevent the counters to be placed on host if(spells.size() < 2) {
if (!host.canReceiveCounters(CounterType.P1P1)) { // If the creature is no longer on the battlefield, the option
return tokenSA; // to add counters is already removed at this point. Return the
} // only available option: create servo tokens.
return spells.get(0);
// if host would leave the play or if host is useless, create tokens }
if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) { SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
return tokenSA;
} // check for something which might prevent the counters to be placed on host
if (!host.canReceiveCounters(CounterType.P1P1)) {
// need a copy for one with extra +1/+1 counter boost, return tokenSA;
// without causing triggers to run }
final Card copy = CardUtil.getLKICopy(host);
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n); // if host would leave the play or if host is useless, create tokens
copy.setZone(host.getZone()); if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) {
return tokenSA;
// if host would put into the battlefield attacking }
if (combat != null && combat.isAttacking(host)) {
final Player defender = combat.getDefenderPlayerByAttacker(host); // need a copy for one with extra +1/+1 counter boost,
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) { // without causing triggers to run
return counterSA; final Card copy = CardUtil.getLKICopy(host);
} copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
return tokenSA; copy.setZone(host.getZone());
}
// if host would put into the battlefield attacking
// if the host has haste and can attack if (combat != null && combat.isAttacking(host)) {
if (CombatUtil.canAttack(copy)) { final Player defender = combat.getDefenderPlayerByAttacker(host);
for (final Player opp : player.getOpponents()) { if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) {
if (CombatUtil.canAttack(copy, opp) && return counterSA;
opp.canLoseLife() && }
!ComputerUtilCard.canBeBlockedProfitably(opp, copy)) return tokenSA;
return counterSA; }
}
} // if the host has haste and can attack
if (CombatUtil.canAttack(copy)) {
// TODO check for trigger to turn token ETB into +1/+1 counter for host for (final Player opp : player.getOpponents()) {
// TODO check for trigger to turn token ETB into damage or life loss for opponent if (CombatUtil.canAttack(copy, opp) &&
// in this cases Token might be prefered even if they would not survive opp.canLoseLife() &&
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true); !ComputerUtilCard.canBeBlockedProfitably(opp, copy))
return counterSA;
// Token would not survive }
if (tokenCard.getNetToughness() < 1) { }
return counterSA;
} // TODO check for trigger to turn token ETB into +1/+1 counter for host
// TODO check for trigger to turn token ETB into damage or life loss for opponent
// Special Card logic, this one try to median its power with the number of artifacts // in this cases Token might be prefered even if they would not survive
if ("Marionette Master".equals(sourceName)) { final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
return list.size() >= copy.getNetPower() ? counterSA : tokenSA; // Token would not survive
} else if ("Cultivator of Blades".equals(sourceName)) { if (tokenCard.getNetToughness() < 1) {
// Cultivator does try to median with number of Creatures return counterSA;
CardCollection list = player.getCreaturesInPlay(); }
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
} // Special Card logic, this one try to median its power with the number of artifacts
if ("Marionette Master".equals(sourceName)) {
// evaluate Creature with +1/+1 CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
int evalCounter = ComputerUtilCard.evaluateCreature(copy); return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
} else if ("Cultivator of Blades".equals(sourceName)) {
final CardCollection tokenList = new CardCollection(host); // Cultivator does try to median with number of Creatures
for (int i = 0; i < n; ++i) { CardCollection list = player.getCreaturesInPlay();
tokenList.add(TokenAi.spawnToken(player, tokenSA)); return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
} }
// evaluate Host with Tokens // evaluate Creature with +1/+1
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList); int evalCounter = ComputerUtilCard.evaluateCreature(copy);
return evalToken >= evalCounter ? tokenSA : counterSA; final CardCollection tokenList = new CardCollection(host);
} else if ("CombustibleGearhulk".equals(logic)) { for (int i = 0; i < n; ++i) {
Player controller = sa.getActivatingPlayer(); tokenList.add(TokenAi.spawnToken(player, tokenSA));
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile"); }
int life = player.getLife();
CardCollectionView revealedCards = controller.getCardsIn(zones); // evaluate Host with Tokens
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
if (revealedCards.size() < 5) {
// Not very many revealed cards, just guess based on lifetotal return evalToken >= evalCounter ? tokenSA : counterSA;
return life < 7 ? spells.get(0) : spells.get(1); } else if ("CombustibleGearhulk".equals(logic)) {
} Player controller = sa.getActivatingPlayer();
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile");
int totalCMC = 0; int life = player.getLife();
for(Card c : revealedCards) { CardCollectionView revealedCards = controller.getCardsIn(zones);
totalCMC += c.getCMC();
} if (revealedCards.size() < 5) {
// Not very many revealed cards, just guess based on lifetotal
int bestGuessDamage = totalCMC * 3 / revealedCards.size(); return life < 7 ? spells.get(0) : spells.get(1);
return life <= bestGuessDamage ? spells.get(0) : spells.get(1); }
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
List<SpellAbility> filtered = Lists.newArrayList(); int totalCMC = 0;
// filter first for the spells which can be done for(Card c : revealedCards) {
for (SpellAbility sp : spells) { totalCMC += c.getCMC();
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(player, sp)) { }
filtered.add(sp);
} int bestGuessDamage = totalCMC * 3 / revealedCards.size();
} return life <= bestGuessDamage ? spells.get(0) : spells.get(1);
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
// TODO find better way to check List<SpellAbility> filtered = Lists.newArrayList();
if (!filtered.isEmpty()) { // filter first for the spells which can be done
return filtered.get(0); for (SpellAbility sp : spells) {
} if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(player, sp)) {
} filtered.add(sp);
return spells.get(0); // return first choice if no logic found }
} }
// TODO find better way to check
if (!filtered.isEmpty()) {
return filtered.get(0);
}
}
return spells.get(0); // return first choice if no logic found
}
} }

View File

@@ -1,34 +1,36 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtil;
import forge.game.player.Player; import forge.ai.SpellAbilityAi;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.util.MyRandom; import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class ChooseNumberAi extends SpellAbilityAi {
public class ChooseNumberAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { @Override
if (!sa.hasParam("AILogic")) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; if (!sa.hasParam("AILogic")) {
} return false;
TargetRestrictions tgt = sa.getTargetRestrictions(); }
if (tgt != null) { TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets(); if (tgt != null) {
if (sa.canTarget(aiPlayer.getOpponent())) { sa.resetTargets();
sa.getTargets().add(aiPlayer.getOpponent()); Player opp = ComputerUtil.getOpponentFor(aiPlayer);
} else { if (sa.canTarget(opp)) {
return false; sa.getTargets().add(opp);
} } else {
} return false;
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); }
return chance; }
} boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return chance;
@Override }
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(ai, sa); @Override
} protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(ai, sa);
} }
}

View File

@@ -1,81 +1,81 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import java.util.List; import java.util.List;
public class ChoosePlayerAi extends SpellAbilityAi { public class ChoosePlayerAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
return true; return true;
} }
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa); return canPlayAI(ai, sa);
} }
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa); return canPlayAI(ai, sa);
} }
@Override @Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) { public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
Player chosen = null; Player chosen = null;
if ("Curse".equals(sa.getParam("AILogic"))) { if ("Curse".equals(sa.getParam("AILogic"))) {
for (Player pc : choices) { for (Player pc : choices) {
if (pc.isOpponentOf(ai)) { if (pc.isOpponentOf(ai)) {
chosen = pc; chosen = pc;
break; break;
} }
} }
if (chosen == null) { if (chosen == null) {
chosen = Iterables.getFirst(choices, null); chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen); System.out.println("No good curse choices. Picking first available: " + chosen);
} }
} }
else if ("Pump".equals(sa.getParam("AILogic"))) { else if ("Pump".equals(sa.getParam("AILogic"))) {
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null); chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
} }
else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) { else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
List<Player> prefChoices = Lists.newArrayList(choices); List<Player> prefChoices = Lists.newArrayList(choices);
prefChoices.removeAll(ai.getOpponents()); prefChoices.removeAll(ai.getOpponents());
if (!prefChoices.isEmpty()) { if (!prefChoices.isEmpty()) {
chosen = ComputerUtil.evaluateBoardPosition(prefChoices); chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
} }
if (chosen == null) { if (chosen == null) {
chosen = Iterables.getFirst(choices, null); chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen); System.out.println("No good curse choices. Picking first available: " + chosen);
} }
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) { } else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
int cardsInHand = 0; int cardsInHand = 0;
for (final Player p : choices) { for (final Player p : choices) {
int hand = p.getCardsIn(ZoneType.Hand).size(); int hand = p.getCardsIn(ZoneType.Hand).size();
if (hand >= cardsInHand) { if (hand >= cardsInHand) {
chosen = p; chosen = p;
cardsInHand = hand; cardsInHand = hand;
} }
} }
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) { } else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
int creats = 50; int creats = 50;
for (final Player p : choices) { for (final Player p : choices) {
int curr = p.getCreaturesInPlay().size(); int curr = p.getCreaturesInPlay().size();
if (curr <= creats) { if (curr <= creats) {
chosen = p; chosen = p;
creats = curr; creats = curr;
} }
} }
} else { } else {
System.out.println("Default player choice logic."); System.out.println("Default player choice logic.");
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null); chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
} }
return chosen; return chosen;
} }
} }

View File

@@ -1,194 +1,230 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Lists; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard; import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtilCard;
import forge.game.Game; import forge.ai.ComputerUtilCombat;
import forge.game.GameObject; import forge.ai.ComputerUtilCost;
import forge.game.ability.AbilityUtils; import forge.ai.SpellAbilityAi;
import forge.game.ability.ApiType; import forge.game.Game;
import forge.game.card.Card; import forge.game.GameObject;
import forge.game.card.CardCollectionView; import forge.game.ability.AbilityUtils;
import forge.game.card.CardLists; import forge.game.ability.ApiType;
import forge.game.combat.Combat; import forge.game.card.Card;
import forge.game.cost.Cost; import forge.game.card.CardCollectionView;
import forge.game.phase.PhaseType; import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.card.CardPredicates;
import forge.game.spellability.SpellAbility; import forge.game.combat.Combat;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.cost.Cost;
import forge.game.spellability.TargetRestrictions; import forge.game.phase.PhaseType;
import forge.game.zone.ZoneType; import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChooseSourceAi extends SpellAbilityAi { import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
/* (non-Javadoc) import forge.game.zone.ZoneType;
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) import forge.util.Aggregates;
*/
@Override public class ChooseSourceAi extends SpellAbilityAi {
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// TODO: AI Support! Currently this is copied from AF ChooseCard. /* (non-Javadoc)
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
// to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage */
// to the player because a CoP was pre-activated on it - unless, of course, there's another @Override
// possible reason to attack with that creature). protected boolean canPlayAI(final Player ai, SpellAbility sa) {
final Card host = sa.getHostCard(); // TODO: AI Support! Currently this is copied from AF ChooseCard.
final Cost abCost = sa.getPayCosts(); // When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
final Card source = sa.getHostCard(); // to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage
// to the player because a CoP was pre-activated on it - unless, of course, there's another
if (abCost != null) { // possible reason to attack with that creature).
// AI currently disabled for these costs final Card host = sa.getHostCard();
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) { final Cost abCost = sa.getPayCosts();
return false; final Card source = sa.getHostCard();
}
if (abCost != null) {
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { // AI currently disabled for these costs
return false; if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
} return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false; if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
} return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false; if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
} return false;
} }
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
if (tgt != null) { return false;
sa.resetTargets(); }
if (sa.canTarget(ai.getOpponent())) { }
sa.getTargets().add(ai.getOpponent());
} else { final TargetRestrictions tgt = sa.getTargetRestrictions();
return false; if (tgt != null) {
} sa.resetTargets();
} Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.hasParam("AILogic")) { if (sa.canTarget(opp)) {
final Game game = ai.getGame(); sa.getTargets().add(opp);
if (sa.getParam("AILogic").equals("NeedsPrevention")) { } else {
if (!game.getStack().isEmpty()) { return false;
final SpellAbility topStack = game.getStack().peekAbility(); }
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) { }
return false; if (sa.hasParam("AILogic")) {
} final Game game = ai.getGame();
final ApiType threatApi = topStack.getApi(); if (sa.getParam("AILogic").equals("NeedsPrevention")) {
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) { if (!game.getStack().isEmpty()) {
return false; final SpellAbility topStack = game.getStack().peekAbility();
} if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) {
return false;
final Card threatSource = topStack.getHostCard(); }
List<? extends GameObject> objects = getTargets(topStack); final ApiType threatApi = topStack.getApi();
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) { if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack); return false;
} }
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) { final Card threatSource = topStack.getHostCard();
return false; List<? extends GameObject> objects = getTargets(topStack);
} if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack); objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) { }
return false;
} if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
return true; return false;
} }
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) { int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
return false; if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
} return false;
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield); }
if (sa.hasParam("Choices")) { return true;
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host); }
} if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
final Combat combat = game.getCombat(); return false;
choices = CardLists.filter(choices, new Predicate<Card>() { }
@Override CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
public boolean apply(final Card c) { if (sa.hasParam("Choices")) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
return false; }
} final Combat combat = game.getCombat();
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0; choices = CardLists.filter(choices, new Predicate<Card>() {
} @Override
}); public boolean apply(final Card c) {
if (choices.isEmpty()) { if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false; return false;
} }
} return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
} }
});
return true; if (choices.isEmpty()) {
} return false;
}
}
}
@Override
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { return true;
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) { }
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
if (!game.getStack().isEmpty()) {
Card choseCard = chooseCardOnStack(sa, ai, game); @Override
if (choseCard != null) { public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return choseCard; if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
} final Player ai = sa.getActivatingPlayer();
} final Game game = ai.getGame();
if (!game.getStack().isEmpty()) {
final Combat combat = game.getCombat(); Card chosenCard = chooseCardOnStack(sa, ai, game);
if (chosenCard != null) {
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() { return chosenCard;
@Override }
public boolean apply(final Card c) { }
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { final Combat combat = game.getCombat();
return false;
} List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0; @Override
} public boolean apply(final Card c) {
}); if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
return ComputerUtilCard.getBestCreatureAI(permanentSources); || combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
} else { }
return ComputerUtilCard.getBestAI(options); return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
} }
} });
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) { // Try to choose the best creature for damage prevention.
for (SpellAbilityStackInstance si : game.getStack()) { Card bestCreature = ComputerUtilCard.getBestCreatureAI(permanentSources);
final Card source = si.getSourceCard(); if (bestCreature != null) {
final SpellAbility abilityOnStack = si.getSpellAbility(true); return bestCreature;
} else {
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) { // No optimal creature was found above, so try to broaden the choice.
continue; if (!Iterables.isEmpty(options)) {
} List<Card> oppCreatures = CardLists.filter(options, Predicates.and(CardPredicates.Presets.CREATURES,
final ApiType threatApi = abilityOnStack.getApi(); Predicates.not(CardPredicates.isOwner(aiChoser))));
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) { List<Card> aiNonCreatures = CardLists.filter(options, Predicates.and(Predicates.not(CardPredicates.Presets.CREATURES),
continue; CardPredicates.Presets.PERMANENTS, CardPredicates.isOwner(aiChoser)));
}
if (!oppCreatures.isEmpty()) {
List<? extends GameObject> objects = getTargets(abilityOnStack); return ComputerUtilCard.getBestCreatureAI(oppCreatures);
} else if (!aiNonCreatures.isEmpty()) {
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers")) return Aggregates.random(aiNonCreatures);
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack); } else {
return Aggregates.random(options);
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) { }
continue; } else if (!game.getStack().isEmpty()) {
} // No permanent for the AI to choose. Should normally not happen unless using dev mode or something,
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack); // but when it does happen, choose the top card on stack if possible (generally it'll be the SA
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) { // source) in order to choose at least something, or the game will hang.
continue; return game.getStack().peekAbility().getHostCard();
} }
return source; }
}
return null; // Should never get here
} System.err.println("Unexpected behavior: The AI was unable to choose anything for AF ChooseSource in "
+ sa.getHostCard() + ", the game will likely hang.");
private static List<GameObject> getTargets(final SpellAbility sa) { return null;
return sa.usesTargeting() && (!sa.hasParam("Defined")) } else {
? Lists.newArrayList(sa.getTargets().getTargets()) return ComputerUtilCard.getBestAI(options);
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa); }
} }
}
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
for (SpellAbilityStackInstance si : game.getStack()) {
final Card source = si.getSourceCard();
final SpellAbility abilityOnStack = si.getSpellAbility(true);
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) {
continue;
}
final ApiType threatApi = abilityOnStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
continue;
}
List<? extends GameObject> objects = getTargets(abilityOnStack);
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
continue;
}
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
continue;
}
return source;
}
return null;
}
private static List<GameObject> getTargets(final SpellAbility sa) {
return sa.usesTargeting() && (!sa.hasParam("Defined"))
? Lists.newArrayList(sa.getTargets().getTargets())
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
}
}

View File

@@ -1,130 +1,130 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiCardMemory; import forge.ai.AiCardMemory;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.card.CardType; import forge.card.CardType;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
import java.util.List; import java.util.List;
public class ChooseTypeAi extends SpellAbilityAi { public class ChooseTypeAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) { if (!sa.hasParam("AILogic")) {
return false; return false;
} else if ("MostProminentComputerControls".equals(sa.getParam("AILogic"))) { } else if ("MostProminentComputerControls".equals(sa.getParam("AILogic"))) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) { if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
return doMirrorEntityLogic(aiPlayer, sa); return doMirrorEntityLogic(aiPlayer, sa);
} }
} }
return doTriggerAINoCost(aiPlayer, sa, false); return doTriggerAINoCost(aiPlayer, sa, false);
} }
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) { private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) { if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) {
return false; return false;
} }
if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) { if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
return false; return false;
} }
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield); CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes()); List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid); String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
if (chosenType.isEmpty()) { if (chosenType.isEmpty()) {
// Account for the situation when only changelings are on the battlefield // Account for the situation when only changelings are on the battlefield
boolean allChangeling = false; boolean allChangeling = false;
for (Card c : otb) { for (Card c : otb) {
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) { if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
chosenType = Aggregates.random(valid); // just choose a random type for changelings chosenType = Aggregates.random(valid); // just choose a random type for changelings
allChangeling = true; allChangeling = true;
break; break;
} }
} }
if (!allChangeling) { if (!allChangeling) {
// Still empty, probably no creatures on board // Still empty, probably no creatures on board
return false; return false;
} }
} }
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa); int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
int avgPower = 0; int avgPower = 0;
// predict the opposition // predict the opposition
CardCollection oppCreatures = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED); CardCollection oppCreatures = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED);
int maxOppPower = 0; int maxOppPower = 0;
int maxOppToughness = 0; int maxOppToughness = 0;
int oppUsefulCreatures = 0; int oppUsefulCreatures = 0;
for (Card oppCre : oppCreatures) { for (Card oppCre : oppCreatures) {
if (ComputerUtilCard.isUselessCreature(aiPlayer, oppCre)) { if (ComputerUtilCard.isUselessCreature(aiPlayer, oppCre)) {
continue; continue;
} }
if (oppCre.getNetPower() > maxOppPower) { if (oppCre.getNetPower() > maxOppPower) {
maxOppPower = oppCre.getNetPower(); maxOppPower = oppCre.getNetPower();
} }
if (oppCre.getNetToughness() > maxOppToughness) { if (oppCre.getNetToughness() > maxOppToughness) {
maxOppToughness = oppCre.getNetToughness(); maxOppToughness = oppCre.getNetToughness();
} }
oppUsefulCreatures++; oppUsefulCreatures++;
} }
if (maxX > 1) { if (maxX > 1) {
CardCollection cre = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield), CardCollection cre = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield),
Predicates.and(CardPredicates.isType(chosenType), CardPredicates.Presets.UNTAPPED)); Predicates.and(CardPredicates.isType(chosenType), CardPredicates.Presets.UNTAPPED));
if (!cre.isEmpty()) { if (!cre.isEmpty()) {
for (Card c: cre) { for (Card c: cre) {
avgPower += c.getNetPower(); avgPower += c.getNetPower();
} }
avgPower /= cre.size(); avgPower /= cre.size();
boolean overpower = cre.size() > oppUsefulCreatures; boolean overpower = cre.size() > oppUsefulCreatures;
if (!overpower) { if (!overpower) {
maxX = Math.max(0, maxX - 3); // conserve some mana unless the board position looks overpowering maxX = Math.max(0, maxX - 3); // conserve some mana unless the board position looks overpowering
} }
if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) { if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) {
sa.setSVar("PayX", String.valueOf(maxX)); sa.setSVar("PayX", String.valueOf(maxX));
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN); AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
return true; return true;
} }
} }
} }
return false; return false;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(ai); sa.getTargets().add(ai);
} else { } else {
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) { for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
if (p.isOpponentOf(ai) && !mandatory) { if (p.isOpponentOf(ai) && !mandatory) {
return false; return false;
} }
} }
} }
return true; return true;
} }
} }

View File

@@ -1,110 +1,110 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection; import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates; import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public class ClashAi extends SpellAbilityAi { public class ClashAi extends SpellAbilityAi {
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean) * @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/ */
@Override @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean legalAction = true; boolean legalAction = true;
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
legalAction = selectTarget(aiPlayer, sa); legalAction = selectTarget(aiPlayer, sa);
} }
return legalAction; return legalAction;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, * @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility) * forge.game.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) { protected boolean checkApiLogic(Player ai, SpellAbility sa) {
boolean legalAction = true; boolean legalAction = true;
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
legalAction = selectTarget(ai, sa); legalAction = selectTarget(ai, sa);
} }
return legalAction; return legalAction;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see forge.ai.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, * @see forge.ai.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable) * forge.game.spellability.SpellAbility, java.lang.Iterable)
*/ */
@Override @Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) { protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
for (Player p : options) { for (Player p : options) {
if (p.getCardsIn(ZoneType.Library).size() == 0) if (p.getCardsIn(ZoneType.Library).size() == 0)
return p; return p;
} }
CardCollectionView col = ai.getCardsIn(ZoneType.Library); CardCollectionView col = ai.getCardsIn(ZoneType.Library);
if (!col.isEmpty() && col.getFirst().mayPlayerLook(ai)) { if (!col.isEmpty() && col.getFirst().mayPlayerLook(ai)) {
final Card top = col.get(0); final Card top = col.get(0);
for (Player p : options) { for (Player p : options) {
final Card oppTop = p.getCardsIn(ZoneType.Library).getFirst(); final Card oppTop = p.getCardsIn(ZoneType.Library).getFirst();
// TODO add logic for SplitCards // TODO add logic for SplitCards
if (top.getCMC() > oppTop.getCMC()) { if (top.getCMC() > oppTop.getCMC()) {
return p; return p;
} }
} }
} }
return Iterables.getFirst(options, null); return Iterables.getFirst(options, null);
} }
private boolean selectTarget(Player ai, SpellAbility sa) { private boolean selectTarget(Player ai, SpellAbility sa) {
String valid = sa.getParam("ValidTgts"); String valid = sa.getParam("ValidTgts");
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa)); PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
// use chooseSinglePlayer function to the select player // use chooseSinglePlayer function to the select player
Player chosen = chooseSinglePlayer(ai, sa, players); Player chosen = chooseSinglePlayer(ai, sa, players);
if (chosen != null) { if (chosen != null) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(chosen); sa.getTargets().add(chosen);
} }
if ("Creature".equals(valid)) { if ("Creature".equals(valid)) {
// Springjack Knight // Springjack Knight
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support // TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats); Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(tgt); sa.getTargets().add(tgt);
} else { } else {
return false; // cut short if this part of the clause is not satisfiable (with current card pool should never get here) return false; // cut short if this part of the clause is not satisfiable (with current card pool should never get here)
} }
} }
return sa.getTargets().getNumTargeted() > 0; return sa.getTargets().getNumTargeted() > 0;
} }
} }

View File

@@ -1,188 +1,195 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.ai.ComputerUtilCard; import forge.game.Game;
import forge.ai.SpellAbilityAi; import forge.game.ability.AbilityUtils;
import forge.game.Game; import forge.game.card.Card;
import forge.game.ability.AbilityUtils; import forge.game.card.CardCollection;
import forge.game.card.Card; import forge.game.card.CardLists;
import forge.game.card.CardCollection; import forge.game.phase.PhaseHandler;
import forge.game.card.CardLists; import forge.game.phase.PhaseType;
import forge.game.phase.PhaseHandler; import forge.game.player.Player;
import forge.game.phase.PhaseType; import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.Player; import forge.game.spellability.SpellAbility;
import forge.game.player.PlayerActionConfirmMode; import forge.game.spellability.TargetRestrictions;
import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType;
import forge.game.spellability.TargetRestrictions;
import java.util.List;
public class CloneAi extends SpellAbilityAi {
public class CloneAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { @Override
final TargetRestrictions tgt = sa.getTargetRestrictions(); protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = source.getGame(); final Card source = sa.getHostCard();
final Game game = source.getGame();
boolean useAbility = true;
boolean useAbility = true;
// if (card.getController().isComputer()) {
// final List<Card> creatures = AllZoneUtil.getCreaturesInPlay(); // if (card.getController().isComputer()) {
// if (!creatures.isEmpty()) { // final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
// cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures); // if (!creatures.isEmpty()) {
// } // cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
// } // }
// }
// TODO - add some kind of check to answer
// "Am I going to attack with this?" // TODO - add some kind of check to answer
// TODO - add some kind of check for during human turn to answer // "Am I going to attack with this?"
// "Can I use this to block something?" // TODO - add some kind of check for during human turn to answer
// "Can I use this to block something?"
PhaseHandler phase = game.getPhaseHandler();
// don't use instant speed clone abilities outside computers PhaseHandler phase = game.getPhaseHandler();
// Combat_Begin step // don't use instant speed clone abilities outside computers
if (!phase.is(PhaseType.COMBAT_BEGIN) // Combat_Begin step
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa) if (!phase.is(PhaseType.COMBAT_BEGIN)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) { && phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
return false; && !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
} return false;
}
// don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step // don't use instant speed clone abilities outside humans
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) { // Combat_Declare_Attackers_InstantAbility step
return false; if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
} return false;
}
// don't activate during main2 unless this effect is permanent
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) { // don't activate during main2 unless this effect is permanent
return false; if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
} return false;
}
if (null == tgt) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); if (null == tgt) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false;
for (final Card c : defined) { boolean bFlag = false;
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn())); for (final Card c : defined) {
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
// for creatures that could be improved (like Figure of Destiny)
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) { // for creatures that could be improved (like Figure of Destiny)
int power = -5; if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
if (sa.hasParam("Power")) { int power = -5;
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa); if (sa.hasParam("Power")) {
} power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
int toughness = -5; }
if (sa.hasParam("Toughness")) { int toughness = -5;
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa); if (sa.hasParam("Toughness")) {
} toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) { }
bFlag = true; if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
} bFlag = true;
} }
}
}
}
if (!bFlag) { // All of the defined stuff is cloned, not very
// useful if (!bFlag) { // All of the defined stuff is cloned, not very
return false; // useful
} return false;
} else { }
sa.resetTargets(); } else {
useAbility &= cloneTgtAI(sa); sa.resetTargets();
} useAbility &= cloneTgtAI(sa);
}
return useAbility;
} // end cloneCanPlayAI() return useAbility;
} // end cloneCanPlayAI()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { @Override
// AI should only activate this during Human's turn public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
boolean chance = true; // AI should only activate this during Human's turn
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa); if (sa.usesTargeting()) {
} chance = cloneTgtAI(sa);
}
return chance;
} return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa); if (sa.usesTargeting()) {
} chance = cloneTgtAI(sa);
}
// Improve AI for triggers. If source is a creature with:
// When ETB, sacrifice a creature. Check to see if the AI has something // Improve AI for triggers. If source is a creature with:
// to sacrifice // When ETB, sacrifice a creature. Check to see if the AI has something
// to sacrifice
// Eventually, we can call the trigger of ETB abilities with
// not mandatory as part of the checks to cast something // Eventually, we can call the trigger of ETB abilities with
// not mandatory as part of the checks to cast something
return chance || mandatory;
} return chance || mandatory;
}
/**
* <p> /**
* cloneTgtAI. * <p>
* </p> * cloneTgtAI.
* * </p>
* @param af *
* a {@link forge.game.ability.AbilityFactory} object. * @param sa
* @param sa * a {@link forge.game.spellability.SpellAbility} object.
* a {@link forge.game.spellability.SpellAbility} object. * @return a boolean.
* @return a boolean. */
*/ private boolean cloneTgtAI(final SpellAbility sa) {
private boolean cloneTgtAI(final SpellAbility sa) { // Specific logic for cards
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or if ("CloneAttacker".equals(sa.getParam("AILogic"))) {
// two are the only things CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard());
// that clone a target. Those can just use SVar:RemAIDeck:True until sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(valid));
// this can do a reasonably return true;
// good job of picking a good target }
return false;
} // Default:
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
/* (non-Javadoc) // two are the only things
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) // that clone a target. Those can just use SVar:RemAIDeck:True until
*/ // this can do a reasonably
@Override // good job of picking a good target
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { return false;
// Didn't confirm in the original code }
return false;
} /* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
/* */
* (non-Javadoc) @Override
* public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, // Didn't confirm in the original code
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean, return false;
* forge.game.player.Player) }
*/
@Override /*
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, * (non-Javadoc)
Player targetedPlayer) { *
final Card host = sa.getHostCard(); * @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
final Player ctrl = host.getController(); * forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
* forge.game.player.Player)
final boolean isVesuva = "Vesuva".equals(host.getName()); */
@Override
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary" protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva"; Player targetedPlayer) {
final Card host = sa.getHostCard();
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); final Player ctrl = host.getController();
if (!newOptions.isEmpty()) {
options = newOptions; final boolean isVesuva = "Vesuva".equals(host.getName());
}
Card choice = ComputerUtilCard.getBestAI(options); final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
if (isVesuva && "Vesuva".equals(choice.getName())) { : "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
choice = null;
} CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
return choice; options = newOptions;
} }
Card choice = ComputerUtilCard.getBestAI(options);
} if (isVesuva && "Vesuva".equals(choice.getName())) {
choice = null;
}
return choice;
}
}

View File

@@ -1,112 +1,113 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtilCard;
import forge.game.ability.AbilityUtils; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.ability.AbilityUtils;
import forge.game.card.CardCollection; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardCollectionView;
import forge.game.player.Player; import forge.game.card.CardLists;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom; import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ControlExchangeAi extends SpellAbilityAi {
public class ControlExchangeAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) /* (non-Javadoc)
*/ * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
@Override */
protected boolean canPlayAI(Player ai, final SpellAbility sa) { @Override
Card object1 = null; protected boolean canPlayAI(Player ai, final SpellAbility sa) {
Card object2 = null; Card object1 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions(); Card object2 = null;
sa.resetTargets(); final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
CardCollection list =
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa); CardCollection list =
// AI won't try to grab cards that are filtered out of AI decks on CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// purpose // AI won't try to grab cards that are filtered out of AI decks on
list = CardLists.filter(list, new Predicate<Card>() { // purpose
@Override list = CardLists.filter(list, new Predicate<Card>() {
public boolean apply(final Card c) { @Override
return !c.hasSVar("RemAIDeck") && c.canBeTargetedBy(sa); public boolean apply(final Card c) {
} return !c.hasSVar("RemAIDeck") && c.canBeTargetedBy(sa);
}); }
object1 = ComputerUtilCard.getBestAI(list); });
if (sa.hasParam("Defined")) { object1 = ComputerUtilCard.getBestAI(list);
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0); if (sa.hasParam("Defined")) {
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) { object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
CardCollectionView list2 = ai.getCardsIn(ZoneType.Battlefield); } else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard(), sa); CardCollectionView list2 = ai.getCardsIn(ZoneType.Battlefield);
object2 = ComputerUtilCard.getWorstAI(list2); list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
sa.getTargets().add(object2); object2 = ComputerUtilCard.getWorstAI(list2);
} sa.getTargets().add(object2);
if (object1 == null || object2 == null) { }
return false; if (object1 == null || object2 == null) {
} return false;
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) { }
sa.getTargets().add(object1); if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); sa.getTargets().add(object1);
} return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return false; }
} return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { @Override
if (!sa.usesTargeting()) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (mandatory) { if (!sa.usesTargeting()) {
return true; if (mandatory) {
} return true;
} else { }
if (mandatory) { } else {
return chkAIDrawback(sa, aiPlayer); if (mandatory) {
} else { return chkAIDrawback(sa, aiPlayer);
return canPlayAI(aiPlayer, sa); } else {
} return canPlayAI(aiPlayer, sa);
} }
return true; }
} return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { @Override
if (!sa.usesTargeting()) { public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return true; if (!sa.usesTargeting()) {
} return true;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa); CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa);
// only select the cards that can be targeted
list = CardLists.getTargetableCards(list, sa); // only select the cards that can be targeted
list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty())
return false; if (list.isEmpty())
return false;
Card best = ComputerUtilCard.getBestAI(list);
Card best = ComputerUtilCard.getBestAI(list);
// if Param has Defined, check if the best Target is better than the Defined
if (sa.hasParam("Defined")) { // if Param has Defined, check if the best Target is better than the Defined
final Card object = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0); if (sa.hasParam("Defined")) {
// TODO add evaluate Land if able final Card object = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
final Card realBest = ComputerUtilCard.getBestAI(Lists.newArrayList(best, object)); // TODO add evaluate Land if able
final Card realBest = ComputerUtilCard.getBestAI(Lists.newArrayList(best, object));
// Defined card is better than this one, try to avoid trade
if (!best.equals(realBest)) { // Defined card is better than this one, try to avoid trade
return false; if (!best.equals(realBest)) {
} return false;
} }
}
// add best Target
sa.getTargets().add(best); // add best Target
return true; sa.getTargets().add(best);
} return true;
}
}
}

View File

@@ -1,317 +1,319 @@
/* /*
* Forge: Play Magic: the Gathering. * Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team * Copyright (C) 2011 Forge Team
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerPredicates; import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx //AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
//GainControl specific sa: //GainControl specific sa:
// LoseControl - the lose control conditions (as a comma separated list) // LoseControl - the lose control conditions (as a comma separated list)
// -Untap - source card becomes untapped // -Untap - source card becomes untapped
// -LoseControl - you lose control of source card // -LoseControl - you lose control of source card
// -LeavesPlay - source card leaves the battlefield // -LeavesPlay - source card leaves the battlefield
// -PowerGT - (not implemented yet for Old Man of the Sea) // -PowerGT - (not implemented yet for Old Man of the Sea)
// AddKWs - Keywords to add to the controlled card // AddKWs - Keywords to add to the controlled card
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword) // (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet // OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
// Untap - set to True if target card should untap when control is taken // Untap - set to True if target card should untap when control is taken
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl // DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt // NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
/** /**
* <p> * <p>
* AbilityFactory_GainControl class. * AbilityFactory_GainControl class.
* </p> * </p>
* *
* @author Forge * @author Forge
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $ * @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
*/ */
public class ControlGainAi extends SpellAbilityAi { public class ControlGainAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) { protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final List<String> lose = Lists.newArrayList(); final List<String> lose = Lists.newArrayList();
if (sa.hasParam("LoseControl")) { if (sa.hasParam("LoseControl")) {
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(","))); lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
} }
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = ai.getGame(); final Game game = ai.getGame();
final FCollectionView<Player> opponents = ai.getOpponents(); final FCollectionView<Player> opponents = ai.getOpponents();
// if Defined, then don't worry about targeting // if Defined, then don't worry about targeting
if (tgt == null) { if (tgt == null) {
if (sa.hasParam("AllValid")) { if (sa.hasParam("AllValid")) {
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents); CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa); tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) { if (tgtCards.isEmpty()) {
return false; return false;
} }
} }
return true; return true;
} else { } else {
sa.resetTargets(); sa.resetTargets();
if (sa.hasParam("TargetingPlayer")) { if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0); Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer); sa.setTargetingPlayer(targetingPlayer);
return targetingPlayer.getController().chooseTargetsFor(sa); return targetingPlayer.getController().chooseTargetsFor(sa);
} }
if (tgt.isRandomTarget()) { if (tgt.isRandomTarget()) {
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false))); sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
} }
if (tgt.canOnlyTgtOpponent()) { if (tgt.canOnlyTgtOpponent()) {
List<Player> oppList = Lists List<Player> oppList = Lists
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa))); .newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) { if (oppList.isEmpty()) {
return false; return false;
} }
sa.getTargets().add(oppList.get(0)); sa.getTargets().add(oppList.get(0));
} }
} }
// Don't steal something if I can't Attack without, or prevent it from // Don't steal something if I can't Attack without, or prevent it from
// blocking at least // blocking at least
if (lose.contains("EOT") if (lose.contains("EOT")
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !sa.isTrigger()) { && !sa.isTrigger()) {
return false; return false;
} }
if (sa.hasParam("Defined")) { if (sa.hasParam("Defined")) {
// no need to target, we'll pick up the target from Defined // no need to target, we'll pick up the target from Defined
return true; return true;
} }
CardCollection list = new CardCollection(); CardCollection list = new CardCollection();
for (Player pl : opponents) { for (Player pl : opponents) {
list.addAll(pl.getCardsIn(ZoneType.Battlefield)); list.addAll(pl.getCardsIn(ZoneType.Battlefield));
} }
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa); list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (list.isEmpty()) { if (list.isEmpty()) {
// no valid targets, so we need to bail // no valid targets, so we need to bail
return false; return false;
} }
// AI won't try to grab cards that are filtered out of AI decks on purpose // AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
if (!c.canBeTargetedBy(sa)) { if (!c.canBeTargetedBy(sa)) {
return false; return false;
} }
if (sa.isTrigger()) { if (sa.isTrigger()) {
return true; return true;
} }
// do not take perm control on something that leaves the play end of turn // do not take perm control on something that leaves the play end of turn
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) { if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
return false; return false;
} }
if (c.isCreature()) { if (c.isCreature()) {
if (c.getNetCombatDamage() <= 0) { if (c.getNetCombatDamage() <= 0) {
return false; return false;
} }
// can not attack any opponent // can not attack any opponent
boolean found = false; boolean found = false;
for (final Player opp : opponents) { for (final Player opp : opponents) {
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) { if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
return false; return false;
} }
} }
// do not take control on something it doesn't know how to use // do not take control on something it doesn't know how to use
return !c.hasSVar("RemAIDeck"); return !c.hasSVar("RemAIDeck");
} }
}); });
if (list.isEmpty()) { if (list.isEmpty()) {
return false; return false;
} }
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0; int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
for (final Card c : list) { for (final Card c : list) {
if (c.isCreature()) { if (c.isCreature()) {
creatures++; creatures++;
} }
if (c.isArtifact()) { if (c.isArtifact()) {
artifacts++; artifacts++;
} }
if (c.isLand()) { if (c.isLand()) {
lands++; lands++;
} }
if (c.isEnchantment()) { if (c.isEnchantment()) {
enchantments++; enchantments++;
} }
if (c.isPlaneswalker()) { if (c.isPlaneswalker()) {
planeswalkers++; planeswalkers++;
} }
} }
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) { while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null; Card t = null;
if (list.isEmpty()) { if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) { if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets(); sa.resetTargets();
return false; return false;
} else { } else {
// TODO is this good enough? for up to amounts? // TODO is this good enough? for up to amounts?
break; break;
} }
} }
while (t == null) { while (t == null) {
if (planeswalkers > 0) { if (planeswalkers > 0) {
t = ComputerUtilCard.getBestPlaneswalkerAI(list); t = ComputerUtilCard.getBestPlaneswalkerAI(list);
} else if (creatures > 0) { } else if (creatures > 0) {
t = ComputerUtilCard.getBestCreatureAI(list); t = ComputerUtilCard.getBestCreatureAI(list);
} else if (artifacts > 0) { } else if (artifacts > 0) {
t = ComputerUtilCard.getBestArtifactAI(list); t = ComputerUtilCard.getBestArtifactAI(list);
} else if (lands > 0) { } else if (lands > 0) {
t = ComputerUtilCard.getBestLandAI(list); t = ComputerUtilCard.getBestLandAI(list);
} else if (enchantments > 0) { } else if (enchantments > 0) {
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true); t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
} else { } else {
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true); t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
} }
if (t.isCreature()) if (t != null) {
creatures--; if (t.isCreature())
if (t.isPlaneswalker()) creatures--;
planeswalkers--; if (t.isPlaneswalker())
if (t.isLand()) planeswalkers--;
lands--; if (t.isLand())
if (t.isArtifact()) lands--;
artifacts--; if (t.isArtifact())
if (t.isEnchantment()) artifacts--;
enchantments--; if (t.isEnchantment())
enchantments--;
if (!sa.canTarget(t)) { }
list.remove(t);
t = null; if (!sa.canTarget(t)) {
if (list.isEmpty()) { list.remove(t);
break; t = null;
} if (list.isEmpty()) {
} break;
}; }
}
if (t != null) { };
sa.getTargets().add(t);
list.remove(t); if (t != null) {
} sa.getTargets().add(t);
} list.remove(t);
}
return true; }
} return true;
@Override }
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) { @Override
if (mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return true; if (sa.getTargetRestrictions() == null) {
} if (mandatory) {
} else { return true;
if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) { }
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); } else {
if (list.isEmpty()) { if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
return false; List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
} else { if (list.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list)); return false;
} } else {
} sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
} }
}
return true; }
}
return true;
@Override }
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
final Game game = ai.getGame(); @Override
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
if (sa.hasParam("AllValid")) { final Game game = ai.getGame();
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa); if (sa.hasParam("AllValid")) {
if (tgtCards.isEmpty()) { CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
return false; tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
} if (tgtCards.isEmpty()) {
} return false;
final List<String> lose = Lists.newArrayList(); }
}
if (sa.hasParam("LoseControl")) { final List<String> lose = Lists.newArrayList();
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
} if (sa.hasParam("LoseControl")) {
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
if (lose.contains("EOT") }
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false; if (lose.contains("EOT")
} && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
} else { return false;
return this.canPlayAI(ai, sa); }
} } else {
return this.canPlayAI(ai, sa);
return true; }
} // pumpDrawbackAI()
return true;
@Override } // pumpDrawbackAI()
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
final List<Card> cards = Lists.newArrayList(); @Override
for (Player p : options) { protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
cards.addAll(p.getCreaturesInPlay()); final List<Card> cards = Lists.newArrayList();
} for (Player p : options) {
Card chosen = ComputerUtilCard.getBestCreatureAI(cards); cards.addAll(p.getCreaturesInPlay());
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null); }
} Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
} return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
}
}

View File

@@ -1,154 +1,195 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Predicate; import com.google.common.collect.Iterables;
import com.google.common.base.Predicates; import forge.ai.*;
import com.google.common.collect.Iterables; import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.ai.ComputerUtil; import forge.game.card.*;
import forge.ai.ComputerUtilCard; import forge.game.card.CardPredicates.Presets;
import forge.ai.SpellAbilityAi; import forge.game.phase.PhaseHandler;
import forge.game.ability.AbilityUtils; import forge.game.phase.PhaseType;
import forge.game.card.Card; import forge.game.player.Player;
import forge.game.card.CardCollection; import forge.game.player.PlayerActionConfirmMode;
import forge.game.card.CardLists; import forge.game.player.PlayerCollection;
import forge.game.card.CardPredicates; import forge.game.spellability.SpellAbility;
import forge.game.card.CardPredicates.Presets; import forge.game.zone.ZoneType;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseType; import java.util.Collection;
import forge.game.player.Player; import java.util.List;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection; public class CopyPermanentAi extends SpellAbilityAi {
import forge.game.spellability.SpellAbility; @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
public class CopyPermanentAi extends SpellAbilityAi { // Card source = sa.getHostCard();
@Override // TODO - I'm sure someone can do this AI better
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// Card source = sa.getHostCard(); PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
// TODO - I'm sure someone can do this AI better String aiLogic = sa.getParamOrDefault("AILogic", "");
if (ComputerUtil.preventRunAwayActivations(sa)) { if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; return false;
} }
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) { if ("MimicVat".equals(aiLogic)) {
return false; return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
} } else if ("AtEOT".equals(aiLogic)) {
return ph.is(PhaseType.END_OF_TURN);
if (sa.hasParam("Defined")) { } else if ("AtOppEOT".equals(aiLogic)) {
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat) return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && sa.getHostCard().getImprintedCards().isEmpty()) { }
return false;
} if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
} return false;
}
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
sa.resetTargets(); if (sa.hasParam("Defined")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0); // If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
sa.setTargetingPlayer(targetingPlayer); if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && sa.getHostCard().getImprintedCards().isEmpty()) {
return targetingPlayer.getController().chooseTargetsFor(sa); return false;
} else { }
return this.doTriggerAINoCost(aiPlayer, sa, false); }
}
} if (sa.hasParam("Embalm") || sa.hasParam("Eternalize")) {
// E.g. Vizier of Many Faces: check to make sure it makes sense to make the token now
@Override if (ComputerUtilCard.checkNeedsToPlayReqs(sa.getHostCard(), sa) != AiPlayDecision.WillPlay) {
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) { return false;
// //// }
// Targeting }
if (sa.usesTargeting()) {
sa.resetTargets(); if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
sa.resetTargets();
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa)); Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);
list = CardLists.filter(list, Predicates.not(CardPredicates.hasSVar("RemAIDeck"))); return targetingPlayer.getController().chooseTargetsFor(sa);
//Nothing to target } else {
if (list.isEmpty()) { return this.doTriggerAINoCost(aiPlayer, sa, false);
return false; }
} }
// Saheeli Rai + Felidar Guardian combo support @Override
if (sa.getHostCard().getName().equals("Saheeli Rai")) { protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian")); final Card host = sa.getHostCard();
if (felidarGuardian.size() > 0) { final Player activator = sa.getActivatingPlayer();
// can copy a Felidar Guardian and combo off, so let's do it final Game game = host.getGame();
sa.getTargets().add(felidarGuardian.get(0)); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
return true;
}
} // ////
// Targeting
// target loop if (sa.usesTargeting()) {
while (sa.canAddMoreTarget()) { sa.resetTargets();
if (list.isEmpty()) {
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) { CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa));
sa.resetTargets();
return false; list = CardLists.filter(list, Predicates.not(CardPredicates.hasSVar("RemAIDeck")));
} else { //Nothing to target
// TODO is this good enough? for up to amounts? if (list.isEmpty()) {
break; return false;
} }
}
// Saheeli Rai + Felidar Guardian combo support
list = CardLists.filter(list, new Predicate<Card>() { if ("Saheeli Rai".equals(sourceName)) {
@Override CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian"));
public boolean apply(final Card c) { if (felidarGuardian.size() > 0) {
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer); // can copy a Felidar Guardian and combo off, so let's do it
} sa.getTargets().add(felidarGuardian.get(0));
}); return true;
Card choice; }
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) { }
if (sa.hasParam("TargetingPlayer")) {
choice = ComputerUtilCard.getWorstCreatureAI(list); // target loop
} else { while (sa.canAddMoreTarget()) {
choice = ComputerUtilCard.getBestCreatureAI(list); if (list.isEmpty()) {
} if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
} else { sa.resetTargets();
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true); return false;
} } else {
// TODO is this good enough? for up to amounts?
if (choice == null) { // can't find anything left break;
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) { }
sa.resetTargets(); }
return false;
} else { list = CardLists.filter(list, new Predicate<Card>() {
// TODO is this good enough? for up to amounts? @Override
break; public boolean apply(final Card c) {
} return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
} }
list.remove(choice); });
sa.getTargets().add(choice); Card choice;
} if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
} else { if (sa.hasParam("TargetingPlayer")) {
// if no targeting, it should always be ok choice = ComputerUtilCard.getWorstCreatureAI(list);
} } else {
choice = ComputerUtilCard.getBestCreatureAI(list);
return true; }
} } else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
/* (non-Javadoc) }
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ if (choice == null) { // can't find anything left
@Override if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { sa.resetTargets();
//TODO: add logic here return false;
return true; } else {
} // TODO is this good enough? for up to amounts?
break;
/* (non-Javadoc) }
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean) }
*/ list.remove(choice);
@Override sa.getTargets().add(choice);
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { }
// Select a card to attach to } else if (sa.hasParam("Choices")) {
return ComputerUtilCard.getBestAI(options); // only check for options, does not select there
} CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
@Override Collection<Card> betterChoices = getBetterOptions(aiPlayer, sa, choices, !mandatory);
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) { if (betterChoices.isEmpty()) {
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay(); return mandatory;
Card chosen = ComputerUtilCard.getBestCreatureAI(cards); }
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null); } else {
} // if no targeting, it should always be ok
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
//TODO: add logic here
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
// Select a card to attach to
CardCollection betterOptions = getBetterOptions(ai, sa, options, isOptional);
if (!betterOptions.isEmpty()) {
options = betterOptions;
}
return ComputerUtilCard.getBestAI(options);
}
private CardCollection getBetterOptions(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
// TODO add filter to not select Legendary from Other Player when ai already have a Legendary with that name
return CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
}
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
}
}

View File

@@ -3,9 +3,11 @@ package forge.ai.ability;
import forge.ai.SpecialCardAi; import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import java.util.List; import java.util.List;
import java.util.Map;
public class CopySpellAbilityAi extends SpellAbilityAi { public class CopySpellAbilityAi extends SpellAbilityAi {
@@ -25,17 +27,31 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) { public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
// NOTE: Other SAs that use CopySpellAbilityAi (e.g. Chain Lightning) are currently routed through // NOTE: Other SAs that use CopySpellAbilityAi (e.g. Chain Lightning) are currently routed through
// generic method SpellAbilityAi#chkDrawbackWithSubs and are handled there. // generic method SpellAbilityAi#chkDrawbackWithSubs and are handled there.
if ("ChainOfSmog".equals(sa.getParam("AILogic"))) { if ("ChainOfSmog".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.ChainOfSmog.consider(aiPlayer, sa); return SpecialCardAi.ChainOfSmog.consider(aiPlayer, sa);
} else if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.ChainOfAcid.consider(aiPlayer, sa);
} }
return super.chkAIDrawback(sa, aiPlayer); return super.chkAIDrawback(sa, aiPlayer);
} }
@Override @Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) { public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
Map<String, Object> params) {
return spells.get(0); 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;
}
} }

View File

@@ -1,299 +1,350 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiController; import forge.ai.*;
import forge.ai.AiProps; import forge.game.Game;
import forge.ai.ComputerUtilAbility; import forge.game.ability.AbilityUtils;
import java.util.Iterator; import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.ai.ComputerUtilCost; import forge.game.card.CardFactoryUtil;
import forge.ai.ComputerUtilMana; import forge.game.cost.Cost;
import forge.ai.PlayerControllerAi; import forge.game.cost.CostDiscard;
import forge.ai.SpecialCardAi; import forge.game.cost.CostExile;
import forge.ai.SpellAbilityAi; import forge.game.cost.CostSacrifice;
import forge.game.Game; import forge.game.player.Player;
import forge.game.ability.AbilityUtils; import forge.game.spellability.SpellAbility;
import forge.game.ability.ApiType; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.card.Card; import forge.game.spellability.TargetRestrictions;
import forge.game.card.CardFactoryUtil; import forge.game.zone.ZoneType;
import forge.game.cost.Cost; import forge.util.MyRandom;
import forge.game.player.Player; import org.apache.commons.lang3.StringUtils;
import forge.game.spellability.SpellAbility; import org.apache.commons.lang3.tuple.ImmutablePair;
import forge.game.spellability.SpellAbilityStackInstance; import org.apache.commons.lang3.tuple.Pair;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom; import java.util.Iterator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair; public class CounterAi extends SpellAbilityAi {
import org.apache.commons.lang3.tuple.Pair;
@Override
public class CounterAi extends SpellAbilityAi { protected boolean canPlayAI(Player ai, SpellAbility sa) {
boolean toReturn = true;
@Override final Cost abCost = sa.getPayCosts();
protected boolean canPlayAI(Player ai, SpellAbility sa) { final Card source = sa.getHostCard();
boolean toReturn = true; final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Cost abCost = sa.getPayCosts(); final Game game = ai.getGame();
final Card source = sa.getHostCard(); int tgtCMC = 0;
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); SpellAbility tgtSA = null;
final Game game = ai.getGame();
int tgtCMC = 0; if (game.getStack().isEmpty()) {
SpellAbility tgtSA = null; return false;
}
if (game.getStack().isEmpty()) {
return false; if (abCost != null) {
} // AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
if (abCost != null) { return false;
// AI currently disabled for these costs }
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) { if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false; return false;
} }
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) { }
return false;
} if ("Force of Will".equals(sourceName)) {
} if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
return false;
if ("Force of Will".equals(sourceName)) { }
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) { }
return false;
} final TargetRestrictions tgt = sa.getTargetRestrictions();
} if (tgt != null) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
if (tgt != null) { if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa); // might as well check for player's friendliness
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai return false;
|| ai.getAllies().contains(topSA.getActivatingPlayer())) { }
// might as well check for player's friendliness
return false; // check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
} if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null || !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa))) {
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa))) { return false;
return false; }
}
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) { return false;
return false; }
}
if ("OppDiscardsHand".equals(sa.getParam("AILogic"))) {
sa.resetTargets(); if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) {
if (sa.canTargetSpellAbility(topSA)) { return false;
sa.getTargets().add(topSA); }
if (topSA.getPayCosts().getTotalMana() != null) { }
tgtSA = topSA;
tgtCMC = topSA.getPayCosts().getTotalMana().getCMC(); sa.resetTargets();
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it? if (sa.canTargetSpellAbility(topSA)) {
} sa.getTargets().add(topSA);
} else { if (topSA.getPayCosts().getTotalMana() != null) {
return false; tgtSA = topSA;
} tgtCMC = topSA.getPayCosts().getTotalMana().getCMC();
} else { tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
return false; }
} } else {
return false;
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null; }
} else {
if (unlessCost != null && !unlessCost.endsWith(">")) { return false;
// Is this Usable Mana Sources? Or Total Available Mana? }
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
int toPay = 0; String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { if (unlessCost != null && !unlessCost.endsWith(">")) {
setPayX = true; Player opp = tgtSA.getActivatingPlayer();
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai); int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa); int toPay = 0;
} boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
if (toPay == 0) { setPayX = true;
return false; toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
} } else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
if (toPay <= usableManaSources) { }
// If this is a reusable Resource, feel free to play it most of
// the time if (toPay == 0) {
if (!SpellAbilityAi.playReusable(ai,sa)) { return false;
return false; }
}
} if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most of
if (setPayX) { // the time
source.setSVar("PayX", Integer.toString(toPay)); if (!SpellAbilityAi.playReusable(ai,sa)) {
} return false;
} }
}
// TODO Improve AI
if (setPayX) {
// Will return true if this spell can counter (or is Reusable and can source.setSVar("PayX", Integer.toString(toPay));
// force the Human into making decisions) }
}
// But really it should be more picky about how it counters things
// TODO Improve AI
if (sa.hasParam("AILogic")) {
String logic = sa.getParam("AILogic"); // Will return true if this spell can counter (or is Reusable and can
if ("Never".equals(logic)) { // force the Human into making decisions)
return false;
} else if (logic.startsWith("MinCMC.")) { // But really it should be more picky about how it counters things
int minCMC = Integer.parseInt(logic.substring(7));
if (tgtCMC < minCMC) { if (sa.hasParam("AILogic")) {
return false; String logic = sa.getParam("AILogic");
} if ("Never".equals(logic)) {
} return false;
} } else if (logic.startsWith("MinCMC.")) {
int minCMC = Integer.parseInt(logic.substring(7));
// Specific constraints for the AI to use/not use counterspells against specific groups of spells if (tgtCMC < minCMC) {
// (specified in the AI profile) return false;
AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); }
boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS); } else if ("NullBrooch".equals(logic)) {
boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS); if (!SpecialCardAi.NullBrooch.consider(ai, sa)) {
boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS); return false;
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS); }
String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS); }
if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) { }
boolean dontCounter = true;
Card tgtSource = tgtSA.getHostCard(); // Specific constraints for the AI to use/not use counterspells against specific groups of spells
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms) // (specified in the AI profile)
|| (tgtSA.getApi() == ApiType.DealDamage || tgtSA.getApi() == ApiType.LoseLife || tgtSA.getApi() == ApiType.DamageAll && ctrDamageSpells) AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|| (tgtSA.getApi() == ApiType.Counter && ctrOtherCounters) boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
|| (tgtSA.getApi() == ApiType.Destroy || tgtSA.getApi() == ApiType.DestroyAll || tgtSA.getApi() == ApiType.Sacrifice boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
|| tgtSA.getApi() == ApiType.SacrificeAll && ctrRemovalSpells)) { boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
dontCounter = false; boolean ctrPumpSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_PUMP_SPELLS);
} boolean ctrAuraSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_AURAS);
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
if (tgtSource != null && !ctrNamed.isEmpty() && !"none".equalsIgnoreCase(ctrNamed)) { int ctrChanceCMC1 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_1);
for (String name : StringUtils.split(ctrNamed, ";")) { int ctrChanceCMC2 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_2);
if (name.equals(tgtSource.getName())) { int ctrChanceCMC3 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_3);
dontCounter = false; String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
} boolean dontCounter = false;
}
} if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) {
dontCounter = true;
// should always counter CMC 1 with Mental Misstep despite a possible limitation by minimum CMC } else if (tgtCMC == 2 && !MyRandom.percentTrue(ctrChanceCMC2)) {
if (tgtCMC == 1 && "Mental Misstep".equals(source.getName())) { dontCounter = true;
dontCounter = false; } else if (tgtCMC == 3 && !MyRandom.percentTrue(ctrChanceCMC3)) {
} dontCounter = true;
}
if (dontCounter) {
return false; if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
} dontCounter = true;
} Card tgtSource = tgtSA.getHostCard();
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
return toReturn; || (tgtSA.getApi() == ApiType.DealDamage || tgtSA.getApi() == ApiType.LoseLife || tgtSA.getApi() == ApiType.DamageAll && ctrDamageSpells)
} || (tgtSA.getApi() == ApiType.Counter && ctrOtherCounters)
|| ((tgtSA.getApi() == ApiType.Pump || tgtSA.getApi() == ApiType.PumpAll) && ctrPumpSpells)
@Override || (tgtSA.getApi() == ApiType.Attach && ctrAuraSpells)
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { || (tgtSA.getApi() == ApiType.Destroy || tgtSA.getApi() == ApiType.DestroyAll || tgtSA.getApi() == ApiType.Sacrifice
return doTriggerAINoCost(aiPlayer, sa, true); || tgtSA.getApi() == ApiType.SacrificeAll && ctrRemovalSpells)) {
} dontCounter = false;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { if (tgtSource != null && !ctrNamed.isEmpty() && !"none".equalsIgnoreCase(ctrNamed)) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); for (String name : StringUtils.split(ctrNamed, ";")) {
final Game game = ai.getGame(); if (name.equals(tgtSource.getName())) {
dontCounter = false;
if (tgt != null) { }
if (game.getStack().isEmpty()) { }
return false; }
}
// should not refrain from countering a CMC X spell if that's the only CMC
sa.resetTargets(); // counterable with that particular counterspell type (e.g. Mental Misstep vs. CMC 1 spells)
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory); if (sa.getParamOrDefault("ValidTgts", "").startsWith("Card.cmcEQ")) {
SpellAbility tgtSA = pair.getLeft(); int validTgtCMC = AbilityUtils.calculateAmount(source, sa.getParam("ValidTgts").substring(10), sa);
if (tgtCMC == validTgtCMC) {
if (tgtSA == null) { dontCounter = false;
return false; }
} }
sa.getTargets().add(tgtSA); }
if (!mandatory && !pair.getRight()) {
// If not mandatory and not preferred, bail out after setting target // Should ALWAYS counter if it doesn't spend a card, otherwise it wastes an opportunity
return false; // to gain card advantage
} if (sa.isAbility()
&& (!sa.getPayCosts().hasSpecificCostType(CostDiscard.class))
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null; && (!sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))
&& (!sa.getPayCosts().hasSpecificCostType(CostExile.class))) {
final Card source = sa.getHostCard(); // TODO: maybe also disallow CostPayLife?
if (unlessCost != null) { dontCounter = false;
// Is this Usable Mana Sources? Or Total Available Mana? }
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
int toPay = 0; // Null Brooch is special - it has a discard cost, but the AI will be
boolean setPayX = false; // discarding no cards, or is playing a deck where discarding is a benefit
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { // as defined in SpecialCardAi.NullBrooch
setPayX = true; if (sa.hasParam("AILogic")) {
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai); if ("NullBrooch".equals(sa.getParam("AILogic"))) {
} else { dontCounter = false;
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa); }
} }
if (!mandatory) { if (dontCounter) {
if (toPay == 0) { return false;
return false; }
}
return toReturn;
if (toPay <= usableManaSources) { }
// If this is a reusable Resource, feel free to play it most
// of the time @Override
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) { public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return false; return doTriggerAINoCost(aiPlayer, sa, true);
} }
}
} @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (setPayX) { final TargetRestrictions tgt = sa.getTargetRestrictions();
source.setSVar("PayX", Integer.toString(toPay)); final Game game = ai.getGame();
}
} if (tgt != null) {
} if (game.getStack().isEmpty()) {
return false;
// TODO Improve AI }
// Will return true if this spell can counter (or is Reusable and can sa.resetTargets();
// force the Human into making decisions) Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
SpellAbility tgtSA = pair.getLeft();
// But really it should be more picky about how it counters things
return true; if (tgtSA == null) {
} return false;
}
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) { sa.getTargets().add(tgtSA);
SpellAbility tgtSA; if (!mandatory && !pair.getRight()) {
SpellAbility leastBadOption = null; // If not mandatory and not preferred, bail out after setting target
SpellAbility bestOption = null; return false;
}
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
SpellAbilityStackInstance si = null; String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
while(it.hasNext()) {
si = it.next(); final Card source = sa.getHostCard();
tgtSA = si.getSpellAbility(true); if (unlessCost != null) {
if (!sa.canTargetSpellAbility(tgtSA)) { Player opp = tgtSA.getActivatingPlayer();
continue; int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
}
if (leastBadOption == null) { int toPay = 0;
leastBadOption = tgtSA; boolean setPayX = false;
} if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) || toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
tgtSA.getActivatingPlayer() == ai || } else {
!tgtSA.getActivatingPlayer().isOpponentOf(ai)) { toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
// Is this a "better" least bad option }
if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) {
// NOOP if (!mandatory) {
} else if (sa.getActivatingPlayer().isOpponentOf(ai)) { if (toPay == 0) {
// Target opponents uncounterable stuff, before our own stuff return false;
leastBadOption = tgtSA; }
}
continue; if (toPay <= usableManaSources) {
} // If this is a reusable Resource, feel free to play it most
// of the time
if (bestOption == null) { if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
bestOption = tgtSA; return false;
} else { }
// TODO Determine if this option is better than the current best option }
boolean betterThanBest = false; }
if (betterThanBest) {
bestOption = tgtSA; if (setPayX) {
} source.setSVar("PayX", Integer.toString(toPay));
// Don't really need to keep updating leastBadOption once we have a bestOption }
} }
} }
return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null); // TODO Improve AI
}
} // Will return true if this spell can counter (or is Reusable and can
// force the Human into making decisions)
// But really it should be more picky about how it counters things
return true;
}
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
SpellAbility tgtSA;
SpellAbility leastBadOption = null;
SpellAbility bestOption = null;
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
SpellAbilityStackInstance si = null;
while(it.hasNext()) {
si = it.next();
tgtSA = si.getSpellAbility(true);
if (!sa.canTargetSpellAbility(tgtSA)) {
continue;
}
if (leastBadOption == null) {
leastBadOption = tgtSA;
}
if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) ||
tgtSA.getActivatingPlayer() == ai ||
!tgtSA.getActivatingPlayer().isOpponentOf(ai)) {
// Is this a "better" least bad option
if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) {
// NOOP
} else if (sa.getActivatingPlayer().isOpponentOf(ai)) {
// Target opponents uncounterable stuff, before our own stuff
leastBadOption = tgtSA;
}
continue;
}
if (bestOption == null) {
bestOption = tgtSA;
} else {
// TODO Determine if this option is better than the current best option
boolean betterThanBest = false;
if (betterThanBest) {
bestOption = tgtSA;
}
// Don't really need to keep updating leastBadOption once we have a bestOption
}
}
return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null);
}
}

View File

@@ -27,6 +27,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.keyword.Keyword;
import forge.util.Aggregates; import forge.util.Aggregates;
@@ -59,7 +60,7 @@ public abstract class CountersAi {
if (type.equals("M1M1")) { if (type.equals("M1M1")) {
// try to kill the best killable creature, or reduce the best one // try to kill the best killable creature, or reduce the best one
// but try not to target a Undying Creature // but try not to target a Undying Creature
final List<Card> killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), "Undying"); final List<Card> killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), Keyword.UNDYING);
if (killable.size() > 0) { if (killable.size() > 0) {
choice = ComputerUtilCard.getBestCreatureAI(killable); choice = ComputerUtilCard.getBestCreatureAI(killable);
} else { } else {

View File

@@ -1,481 +1,490 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import com.google.common.base.Predicate;
import java.util.Map; import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import com.google.common.base.Predicate; import forge.ai.ComputerUtilCard;
import com.google.common.collect.Iterables; import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.ai.ComputerUtil; import forge.game.ability.AbilityUtils;
import forge.ai.ComputerUtilCard; import forge.game.card.*;
import forge.ai.SpellAbilityAi; import forge.game.keyword.Keyword;
import forge.game.Game; import forge.game.phase.PhaseHandler;
import forge.game.ability.AbilityUtils; import forge.game.phase.PhaseType;
import forge.game.card.Card; import forge.game.player.Player;
import forge.game.card.CardLists; import forge.game.spellability.SpellAbility;
import forge.game.card.CardPredicates; import forge.game.zone.ZoneType;
import forge.game.card.CardUtil; import forge.util.MyRandom;
import forge.game.card.CounterType; import forge.util.collect.FCollection;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import java.util.List;
import forge.game.player.Player; import java.util.Map;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; public class CountersMoveAi extends SpellAbilityAi {
import forge.util.MyRandom; @Override
import forge.util.collect.FCollection; protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
public class CountersMoveAi extends SpellAbilityAi { if (sa.usesTargeting()) {
@Override sa.resetTargets();
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { if (!moveTgtAI(ai, sa)) {
return false;
if (sa.usesTargeting()) { }
sa.resetTargets(); }
if (!moveTgtAI(ai, sa)) {
return false; if (!SpellAbilityAi.playReusable(ai, sa)) {
} return false;
} }
if (!SpellAbilityAi.playReusable(ai, sa)) { return MyRandom.getRandom().nextFloat() < .8f; // random success
return false; }
}
@Override
return MyRandom.getRandom().nextFloat() < .8f; // random success protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
} final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
@Override final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card host = sa.getHostCard(); // Don't tap creatures that may be able to block
final String type = sa.getParam("CounterType"); if (ComputerUtil.waitForBlocking(sa)) {
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type); return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) { if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
return false; int amount = calcAmount(sa, cType);
} final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (ph.getPlayerTurn().isOpponentOf(ai)) {
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) { // opponent Creature with +1/+1 counter does attack
int amount = calcAmount(sa, cType); // try to steal counter from it to kill it
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa); if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
if (ph.getPlayerTurn().isOpponentOf(ai)) { for (final Card c : srcCards) {
// opponent Creature with +1/+1 counter does attack // source is not controlled by current player
// try to steal counter from it to kill it if (!ph.isPlayerTurn(c.getController())) {
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) { continue;
for (final Card c : srcCards) { }
// source is not controlled by current player
if (!ph.isPlayerTurn(c.getController())) { int a = c.getCounters(cType);
continue; if (a < amount) {
} continue;
}
int a = c.getCounters(cType); if (ph.getCombat().isAttacking(c)) {
if (a < amount) { // get copy of creature with removed Counter
continue; final Card cpy = CardUtil.getLKICopy(c);
} // cant use substract on Copy
if (ph.getCombat().isAttacking(c)) { cpy.setCounters(cType, a - amount);
// get copy of creature with removed Counter
final Card cpy = CardUtil.getLKICopy(c); // a removed counter would kill it
// cant use substract on Copy if (cpy.getNetToughness() <= cpy.getDamage()) {
cpy.setCounters(cType, a - amount); return true;
}
// a removed counter would kill it
if (cpy.getNetToughness() <= cpy.getDamage()) { // something you can't block, try to reduce its
return true; // attack
} if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
return true;
// something you can't block, try to reduce its }
// attack }
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) { }
return true; return false;
} }
}
} }
return false;
} // for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
} return false;
}
// for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) { } else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
return false; // something like Cyptoplast Root-kin
} if (ph.getPlayerTurn().isOpponentOf(ai)) {
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
// something like Cyptoplast Root-kin }
if (ph.getPlayerTurn().isOpponentOf(ai)) { }
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) { // for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
} return false;
} }
// for Simic Fluxmage and other // Make sure that removing the last counter doesn't kill the creature
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) { if ("Self".equals(sa.getParam("Source"))) {
return false; if (host != null && host.getNetToughness() - 1 <= 0) {
} return false;
} }
return true; }
} }
return true;
@Override }
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
@Override
if (sa.usesTargeting()) { protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
if (!moveTgtAI(ai, sa) && !mandatory) { if (sa.usesTargeting()) {
return false;
} if (!moveTgtAI(ai, sa) && !mandatory) {
return false;
if (!sa.isTargetNumberValid() && mandatory) { }
final Game game = ai.getGame();
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa); if (!sa.isTargetNumberValid() && mandatory) {
final Game game = ai.getGame();
if (tgtCards.isEmpty()) { List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
return false;
} if (tgtCards.isEmpty()) {
return false;
final Card card = ComputerUtilCard.getWorstAI(tgtCards); }
sa.getTargets().add(card);
} final Card card = ComputerUtilCard.getWorstAI(tgtCards);
return true; sa.getTargets().add(card);
} else { }
// no target Probably something like Graft return true;
} else {
if (mandatory) { // no target Probably something like Graft
return true;
} if (mandatory) {
return true;
final Card host = sa.getHostCard(); }
final String type = sa.getParam("CounterType"); final Card host = sa.getHostCard();
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
final String type = sa.getParam("CounterType");
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa); final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (srcCards.isEmpty() || destCards.isEmpty()) { final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
return false;
} if (srcCards.isEmpty() || destCards.isEmpty()) {
return false;
final Card src = srcCards.get(0); }
final Card dest = destCards.get(0);
final Card src = srcCards.get(0);
// for such Trigger, do not move counter to another players creature final Card dest = destCards.get(0);
if (!dest.getController().equals(ai)) {
return false; // for such Trigger, do not move counter to another players creature
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) { if (!dest.getController().equals(ai)) {
return false; return false;
} else if (dest.hasSVar("EndOfTurnLeavePlay")) { } else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
return false; return false;
} } else if (dest.hasSVar("EndOfTurnLeavePlay")) {
return false;
if (cType != null) { }
if (!dest.canReceiveCounters(cType)) {
return false; if (cType != null) {
} if (!dest.canReceiveCounters(cType)) {
final int amount = calcAmount(sa, cType); return false;
int a = src.getCounters(cType); }
if (a < amount) { final int amount = calcAmount(sa, cType);
return false; int a = src.getCounters(cType);
} if (a < amount) {
return false;
final Card srcCopy = CardUtil.getLKICopy(src); }
// cant use substract on Copy
srcCopy.setCounters(cType, a - amount); final Card srcCopy = CardUtil.getLKICopy(src);
// cant use substract on Copy
final Card destCopy = CardUtil.getLKICopy(dest); srcCopy.setCounters(cType, a - amount);
destCopy.setCounters(cType, dest.getCounters(cType) + amount);
final Card destCopy = CardUtil.getLKICopy(dest);
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest); destCopy.setCounters(cType, dest.getCounters(cType) + amount);
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest);
if (newEval < oldEval) { int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
return false;
} if (newEval < oldEval) {
} return false;
// no target }
return true;
} // check for some specific AI preferences
} if (src.hasStartOfKeyword("Graft") && "DontMoveCounterIfLethal".equals(src.getSVar("AIGraftPreference"))) {
if (cType == CounterType.P1P1 && src.getNetToughness() - src.getTempToughnessBoost() - 1 <= 0) {
@Override return false;
public boolean chkAIDrawback(SpellAbility sa, Player ai) { }
if (sa.usesTargeting()) { }
sa.resetTargets(); }
if (!moveTgtAI(ai, sa)) { // no target
return false; return true;
} }
} }
return true; @Override
} public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (sa.usesTargeting()) {
private static int calcAmount(final SpellAbility sa, final CounterType cType) { sa.resetTargets();
final Card host = sa.getHostCard(); if (!moveTgtAI(ai, sa)) {
return false;
final String amountStr = sa.getParam("CounterNum"); }
}
// TODO handle proper calculation of X values based on Cost
int amount = 0; return true;
}
if (amountStr.equals("All") || amountStr.equals("Any")) {
// sa has Source, otherwise Source is the Target private static int calcAmount(final SpellAbility sa, final CounterType cType) {
if (sa.hasParam("Source")) { final Card host = sa.getHostCard();
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
for (final Card c : srcCards) { final String amountStr = sa.getParam("CounterNum");
amount += c.getCounters(cType);
} // TODO handle proper calculation of X values based on Cost
} int amount = 0;
} else {
amount = AbilityUtils.calculateAmount(host, amountStr, sa); if (amountStr.equals("All") || amountStr.equals("Any")) {
} // sa has Source, otherwise Source is the Target
return amount; if (sa.hasParam("Source")) {
} final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
for (final Card c : srcCards) {
private boolean moveTgtAI(final Player ai, final SpellAbility sa) { amount += c.getCounters(cType);
}
final Card host = sa.getHostCard(); }
final Game game = ai.getGame(); } else {
final String type = sa.getParam("CounterType"); amount = AbilityUtils.calculateAmount(host, amountStr, sa);
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type); }
return amount;
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa); }
if (sa.hasParam("Defined")) { private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
final int amount = calcAmount(sa, cType);
tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType)); final Card host = sa.getHostCard();
final Game game = ai.getGame();
// SA uses target for Source final String type = sa.getParam("CounterType");
// Target => Defined final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (destCards.isEmpty()) {
// something went wrong if (sa.hasParam("Defined")) {
return false; final int amount = calcAmount(sa, cType);
} tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType));
final Card dest = destCards.get(0); // SA uses target for Source
// Target => Defined
// remove dest from targets, because move doesn't work that way final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
tgtCards.remove(dest);
if (destCards.isEmpty()) {
if (cType != null && !dest.canReceiveCounters(cType)) { // something went wrong
return false; return false;
} }
// prefered logic for this: try to steal counter final Card dest = destCards.get(0);
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) { // remove dest from targets, because move doesn't work that way
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() { tgtCards.remove(dest);
@Override if (cType != null && !dest.canReceiveCounters(cType)) {
public boolean apply(Card card) { return false;
// do not weak a useless creature if able }
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false; // prefered logic for this: try to steal counter
} List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
final Card srcCardCpy = CardUtil.getLKICopy(card); List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount); @Override
public boolean apply(Card card) {
// do not steal a P1P1 from Undying if it would die // do not weak a useless creature if able
// this way if (ComputerUtilCard.isUselessCreature(ai, card)) {
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) { return false;
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying") || card.isToken()) { }
return true;
} final Card srcCardCpy = CardUtil.getLKICopy(card);
return false; // cant use substract on Copy
} srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
return true;
} // do not steal a P1P1 from Undying if it would die
// this way
}); if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken()) {
// if no Prefered found, try normal list return true;
if (best.isEmpty()) { }
best = oppList; return false;
} }
return true;
Card card = ComputerUtilCard.getBestCreatureAI(best); }
if (card != null) { });
sa.getTargets().add(card);
return true; // if no Prefered found, try normal list
} if (best.isEmpty()) {
best = oppList;
} }
// from your creature, try to take from the weakest Card card = ComputerUtilCard.getBestCreatureAI(best);
FCollection<Player> ally = ai.getAllies();
ally.add(ai); if (card != null) {
sa.getTargets().add(card);
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally); return true;
if (!aiList.isEmpty()) { }
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
}
@Override
public boolean apply(Card card) { // from your creature, try to take from the weakest
// gain from useless FCollection<Player> ally = ai.getAllies();
if (ComputerUtilCard.isUselessCreature(ai, card)) { ally.add(ai);
return true;
} List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally);
if (!aiList.isEmpty()) {
// source would leave the game List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
if (card.hasSVar("EndOfTurnLeavePlay")) {
return true; @Override
} public boolean apply(Card card) {
// gain from useless
// try to remove P1P1 from undying or evolve if (ComputerUtilCard.isUselessCreature(ai, card)) {
if (CounterType.P1P1.equals(cType)) { return true;
if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) { }
return true;
} // source would leave the game
} if (card.hasSVar("EndOfTurnLeavePlay")) {
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) { return true;
return true; }
}
// try to remove P1P1 from undying or evolve
return false; if (CounterType.P1P1.equals(cType)) {
} if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) {
}); return true;
}
if (best.isEmpty()) { }
best = aiList; if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
} return true;
}
Card card = ComputerUtilCard.getWorstCreatureAI(best);
return false;
if (card != null) { }
sa.getTargets().add(card); });
return true;
} if (best.isEmpty()) {
} best = aiList;
}
return false;
} else { Card card = ComputerUtilCard.getWorstCreatureAI(best);
// SA uses target for Defined
// Source => Targeted if (card != null) {
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa); sa.getTargets().add(card);
return true;
if (srcCards.isEmpty()) { }
// something went wrong }
return false;
} return false;
} else {
final Card src = srcCards.get(0); // SA uses target for Defined
if (cType != null) { // Source => Targeted
if (src.getCounters(cType) <= 0) { final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
return false;
} if (srcCards.isEmpty()) {
} // something went wrong
return false;
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai); }
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() { final Card src = srcCards.get(0);
if (cType != null) {
@Override if (src.getCounters(cType) <= 0) {
public boolean apply(Card card) { return false;
// gain from useless }
if (ComputerUtilCard.isUselessCreature(ai, card)) { }
return false;
} List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) {
// source would leave the game List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
if (card.hasSVar("EndOfTurnLeavePlay")) {
return false; @Override
} public boolean apply(Card card) {
// gain from useless
if (cType != null) { if (ComputerUtilCard.isUselessCreature(ai, card)) {
if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) { return false;
return false; }
}
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) { // source would leave the game
return false; if (card.hasSVar("EndOfTurnLeavePlay")) {
} return false;
}
if (!card.canReceiveCounters(cType)) {
return false; if (cType != null) {
} if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) {
} return false;
return false; }
} if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
}); return false;
}
if (best.isEmpty()) {
best = aiList; if (!card.canReceiveCounters(cType)) {
} return false;
}
Card card = ComputerUtilCard.getBestCreatureAI(best); }
return false;
if (card != null) { }
sa.getTargets().add(card); });
return true;
} if (best.isEmpty()) {
} best = aiList;
}
// move counter to opponents creature but only if you can not steal
// them Card card = ComputerUtilCard.getBestCreatureAI(best);
// try to move to something useless or something that would leave
// play if (card != null) {
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents()); sa.getTargets().add(card);
if (!oppList.isEmpty()) { return true;
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() { }
}
@Override
public boolean apply(Card card) { // move counter to opponents creature but only if you can not steal
// gain from useless // them
if (!ComputerUtilCard.isUselessCreature(ai, card)) { // try to move to something useless or something that would leave
return true; // play
} List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
// source would leave the game List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
if (!card.hasSVar("EndOfTurnLeavePlay")) {
return true; @Override
} public boolean apply(Card card) {
// gain from useless
return false; if (!ComputerUtilCard.isUselessCreature(ai, card)) {
} return true;
}); }
if (best.isEmpty()) { // source would leave the game
best = aiList; if (!card.hasSVar("EndOfTurnLeavePlay")) {
} return true;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
return false;
if (card != null) { }
sa.getTargets().add(card); });
return true;
} if (best.isEmpty()) {
} best = aiList;
return false; }
}
} Card card = ComputerUtilCard.getBestCreatureAI(best);
// used for multiple sources -> defied if (card != null) {
// or for source -> multiple defined sa.getTargets().add(card);
@Override return true;
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, }
Player targetedPlayer) { }
if (sa.hasParam("AiLogic")) { return false;
String logic = sa.getParam("AiLogic"); }
}
if ("ToValid".equals(logic)) {
// cards like Forgotten Ancient // used for multiple sources -> defied
// can put counter on any creature, but should only put one on // or for source -> multiple defined
// Ai controlled ones @Override
List<Card> aiCards = CardLists.filterControlledBy(options, ai); protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
return ComputerUtilCard.getBestCreatureAI(aiCards); Player targetedPlayer) {
} else if ("FromValid".equals(logic)) { if (sa.hasParam("AiLogic")) {
// cards like Aetherborn Marauder String logic = sa.getParam("AiLogic");
return ComputerUtilCard.getWorstCreatureAI(options);
} if ("ToValid".equals(logic)) {
} // cards like Forgotten Ancient
return Iterables.getFirst(options, null); // can put counter on any creature, but should only put one on
} // Ai controlled ones
List<Card> aiCards = CardLists.filterControlledBy(options, ai);
// used when selecting how many counters to move return ComputerUtilCard.getBestCreatureAI(aiCards);
@Override } else if ("FromValid".equals(logic)) {
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) { // cards like Aetherborn Marauder
// TODO improve logic behind it return ComputerUtilCard.getWorstCreatureAI(options);
// like keeping the last counter on a 0/0 creature }
return max; }
} return Iterables.getFirst(options, null);
} }
// used when selecting how many counters to move
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
// TODO improve logic behind it
// like keeping the last counter on a 0/0 creature
return max;
}
}

View File

@@ -1,98 +1,98 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public class CountersProliferateAi extends SpellAbilityAi { public class CountersProliferateAi extends SpellAbilityAi {
@Override @Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) { protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final List<Card> cperms = Lists.newArrayList(); final List<Card> cperms = Lists.newArrayList();
final List<Player> allies = ai.getAllies(); final List<Player> allies = ai.getAllies();
allies.add(ai); allies.add(ai);
boolean allyExpOrEnergy = false; boolean allyExpOrEnergy = false;
for (final Player p : allies) { for (final Player p : allies) {
// player has experience or energy counter // player has experience or energy counter
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) { if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
allyExpOrEnergy = true; allyExpOrEnergy = true;
} }
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() { cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override @Override
public boolean apply(final Card crd) { public boolean apply(final Card crd) {
if (crd.hasCounters()) { if (crd.hasCounters()) {
return false; return false;
} }
// iterate only over existing counters // iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) { for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) { if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true; return true;
} }
} }
return false; return false;
} }
})); }));
} }
final List<Card> hperms = Lists.newArrayList(); final List<Card> hperms = Lists.newArrayList();
boolean opponentPoison = false; boolean opponentPoison = false;
for (final Player o : ai.getOpponents()) { for (final Player o : ai.getOpponents()) {
opponentPoison |= o.getPoisonCounters() >= 1; opponentPoison |= o.getPoisonCounters() >= 1;
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() { hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override @Override
public boolean apply(final Card crd) { public boolean apply(final Card crd) {
if (crd.hasCounters()) { if (crd.hasCounters()) {
return false; return false;
} }
// iterate only over existing counters // iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) { for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) { if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true; return true;
} }
} }
return false; return false;
} }
})); }));
} }
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) { if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
return false; return false;
} }
return true; return true;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true; boolean chance = true;
// TODO Make sure Human has poison counters or there are some counters // TODO Make sure Human has poison counters or there are some counters
// we want to proliferate // we want to proliferate
return chance; return chance;
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player) * @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/ */
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa); return canPlayAI(ai, sa);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,143 +1,181 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCost; import com.google.common.collect.Lists;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtilCost;
import forge.game.ability.AbilityUtils; import forge.ai.ComputerUtilMana;
import forge.game.card.Card; import forge.ai.SpellAbilityAi;
import forge.game.card.CardLists; import forge.game.ability.AbilityUtils;
import forge.game.cost.Cost; import forge.game.card.Card;
import forge.game.phase.PhaseHandler; import forge.game.card.CardLists;
import forge.game.phase.PhaseType; import forge.game.cost.Cost;
import forge.game.player.Player; import forge.game.phase.PhaseHandler;
import forge.game.player.PlayerActionConfirmMode; import forge.game.phase.PhaseType;
import forge.game.spellability.AbilitySub; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.AbilitySub;
import forge.game.zone.ZoneType; import forge.game.spellability.SpellAbility;
import forge.util.MyRandom; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List; import forge.util.MyRandom;
import java.util.Random;
import java.util.List;
public class CountersPutAllAi extends SpellAbilityAi {
@Override public class CountersPutAllAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) { @Override
// AI needs to be expanded, since this function can be pretty complex protected boolean canPlayAI(Player ai, SpellAbility sa) {
// based on what // AI needs to be expanded, since this function can be pretty complex
// the expected targets could be // based on what
final Random r = MyRandom.getRandom(); // the expected targets could be
final Cost abCost = sa.getPayCosts(); final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
List<Card> hList; List<Card> hList;
List<Card> cList; List<Card> cList;
final String type = sa.getParam("CounterType"); final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum"); final String amountStr = sa.getParam("CounterNum");
final String valid = sa.getParam("ValidCards"); final String valid = sa.getParam("ValidCards");
final boolean curse = sa.isCurse(); final String logic = sa.getParamOrDefault("AILogic", "");
final TargetRestrictions tgt = sa.getTargetRestrictions(); final boolean curse = sa.isCurse();
final TargetRestrictions tgt = sa.getTargetRestrictions();
hList = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
cList = CardLists.getValidCards(ai.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) {
// AI currently disabled for these costs if (abCost != null) {
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) { // AI currently disabled for these costs
return false; if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
} return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false; if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
} return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false; if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
} return false;
} }
}
if (tgt != null) {
Player pl = curse ? ai.getOpponent() : ai; if (logic.equals("AtEOTOrBlock")) {
sa.getTargets().add(pl); if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
hList = CardLists.filterControlledBy(hList, pl); }
cList = CardLists.filterControlledBy(cList, pl); } else if (logic.equals("AtOppEOT")) {
} if (!(ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn() == ai)) {
return false;
// TODO improve X value to don't overpay when extra mana won't do }
// anything more useful }
final int amount;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { if (tgt != null) {
// Set PayX here to maximum value. Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
amount = ComputerUtilMana.determineLeftoverMana(sa, ai); sa.getTargets().add(pl);
source.setSVar("PayX", Integer.toString(amount));
} else { hList = CardLists.filterControlledBy(hList, pl);
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); cList = CardLists.filterControlledBy(cList, pl);
} }
// prevent run-away activations - first time will always return true // TODO improve X value to don't overpay when extra mana won't do
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); // anything more useful
final int amount;
if (curse) { if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
if (type.equals("M1M1")) { // Set PayX here to maximum value.
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() { amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
@Override source.setSVar("PayX", Integer.toString(amount));
public boolean apply(final Card c) { } else {
return c.getNetToughness() <= amount; amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
} }
});
if (!(killable.size() > 2)) { // prevent run-away activations - first time will always return true
return false; boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
} else { if (curse) {
// make sure compy doesn't harm his stuff more than human's if (type.equals("M1M1")) {
// stuff final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
if (cList.size() > hList.size()) { @Override
return false; public boolean apply(final Card c) {
} return c.getNetToughness() <= amount;
} }
} else { });
// human has more things that will benefit, don't play if (!(killable.size() > 2)) {
if (hList.size() >= cList.size()) { return false;
return false; }
} } else {
// make sure compy doesn't harm his stuff more than human's
//Check for cards that could profit from the ability // stuff
PhaseHandler phase = ai.getGame().getPhaseHandler(); if (cList.size() > hList.size()) {
if (type.equals("P1P1") && sa.isAbility() && source.isCreature() return false;
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost() }
&& sa instanceof AbilitySub }
&& (!phase.getNextTurn().equals(ai) } else {
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) { // human has more things that will benefit, don't play
boolean combatants = false; if (hList.size() >= cList.size()) {
for (Card c : hList) { return false;
if (!c.equals(source) && c.isUntapped()) { }
combatants = true;
break; //Check for cards that could profit from the ability
} PhaseHandler phase = ai.getGame().getPhaseHandler();
} if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
if (!combatants) { && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
return false; && sa instanceof AbilitySub
} && (!phase.getNextTurn().equals(ai)
} || phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
} boolean combatants = false;
for (Card c : hList) {
if (SpellAbilityAi.playReusable(ai, sa)) { if (!c.equals(source) && c.isUntapped()) {
return chance; combatants = true;
} break;
}
return ((r.nextFloat() < .6667) && chance); }
} if (!combatants) {
return false;
@Override }
public boolean chkAIDrawback(SpellAbility sa, Player ai) { }
return canPlayAI(ai, sa); }
}
/* (non-Javadoc) if (SpellAbilityAi.playReusable(ai, sa)) {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) return chance;
*/ }
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
return player.getCreaturesInPlay().size() >= player.getOpponent().getCreaturesInPlay().size(); }
}
} @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (sa.usesTargeting()) {
List<Player> players = Lists.newArrayList();
if (!sa.isCurse()) {
players.add(aiPlayer);
}
players.addAll(aiPlayer.getOpponents());
players.addAll(aiPlayer.getAllies());
if (sa.isCurse()) {
players.add(aiPlayer);
}
for (final Player p : players) {
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
boolean preferred = false;
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
sa.resetTargets();
sa.getTargets().add(p);
return preferred || mandatory;
}
}
}
return mandatory;
}
}

View File

@@ -1,297 +1,287 @@
/* /*
* Forge: Play Magic: the Gathering. * Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team * Copyright (C) 2011 Forge Team
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import forge.ai.ComputerUtil;
import java.util.Map; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.ai.ComputerUtil; import forge.game.Game;
import forge.ai.ComputerUtilCard; import forge.game.GlobalRuleChange;
import forge.ai.SpellAbilityAi; import forge.game.card.*;
import forge.game.Game; import forge.game.keyword.Keyword;
import forge.game.GlobalRuleChange; import forge.game.player.Player;
import forge.game.card.Card; import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.card.CardCollection; import forge.game.spellability.SpellAbility;
import forge.game.card.CardCollectionView; import forge.game.spellability.TargetRestrictions;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import java.util.List;
import forge.game.card.CounterType; import java.util.Map;
import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType; /**
import forge.game.spellability.SpellAbility; * <p>
import forge.game.spellability.TargetRestrictions; * AbilityFactory_PutOrRemoveCountersAi class.
* </p>
/** *
* <p> * @author Forge
* AbilityFactory_PutOrRemoveCountersAi class. * @version $Id$
* </p> */
* public class CountersPutOrRemoveAi extends SpellAbilityAi {
* @author Forge
* @version $Id$ /*
*/ * (non-Javadoc)
public class CountersPutOrRemoveAi extends SpellAbilityAi { *
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
/* * forge.game.spellability.SpellAbility)
* (non-Javadoc) */
* @Override
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, protected boolean checkApiLogic(Player ai, SpellAbility sa) {
* forge.game.spellability.SpellAbility) if (sa.usesTargeting()) {
*/ return doTgt(ai, sa, false);
@Override }
protected boolean checkApiLogic(Player ai, SpellAbility sa) { return super.checkApiLogic(ai, sa);
if (sa.usesTargeting()) { }
return doTgt(ai, sa, false);
} private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
return super.checkApiLogic(ai, sa); final Game game = ai.getGame();
}
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard(); // remove counter with Time might use Exile Zone too
final Game game = ai.getGame(); final TargetRestrictions tgt = sa.getTargetRestrictions();
// need to targetable
final int amount = Integer.valueOf(sa.getParam("CounterNum")); CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
// remove counter with Time might use Exile Zone too if (list.isEmpty()) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); return false;
// need to targetable }
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
// Filter AI-specific targets if provided
if (list.isEmpty()) { list = ComputerUtil.filterAITgts(sa, ai, list, false);
return false;
} if (sa.hasParam("CounterType")) {
// currently only Jhoira's Timebug
if (sa.hasParam("AITgts")) { final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
String aiTgts = sa.getParam("AITgts");
CardCollection prefList = CardLists.getValidCards(list, aiTgts.split(","), ai, source, sa); CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
if (!prefList.isEmpty() || sa.hasParam("AITgtsStrict")) {
list = prefList; if (countersList.isEmpty()) {
} return false;
} }
if (sa.hasParam("CounterType")) { // currently can only target cards you control or you own
// currently only Jhoira's Timebug final Card best = ComputerUtilCard.getBestAI(countersList);
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
// currently both cards only has one target
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount)); sa.getTargets().add(best);
return true;
if (countersList.isEmpty()) { } else {
return false; // currently only Clockspinning
} boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
// currently can only target cards you control or you own // logic to remove some counter
final Card best = ComputerUtilCard.getBestAI(countersList); CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
// currently both cards only has one target if (!countersList.isEmpty()) {
sa.getTargets().add(best);
return true; if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
} else { CardCollectionView depthsList = CardLists.filter(countersList,
// currently only Clockspinning CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (!depthsList.isEmpty()) {
// logic to remove some counter sa.getTargets().add(depthsList.getFirst());
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters()); return true;
}
if (!countersList.isEmpty()) { }
if (!ai.isCardInPlay("Marit Lage") || noLegendary) { // Get rid of Planeswalkers, currently only if it can kill them
CardCollectionView depthsList = CardLists.filter(countersList, // with one touch
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE)); CardCollection planeswalkerList = CardLists.filter(
CardLists.filterControlledBy(countersList, ai.getOpponents()),
if (!depthsList.isEmpty()) { CardPredicates.Presets.PLANESWALKERS,
sa.getTargets().add(depthsList.getFirst()); CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
return true;
} if (!planeswalkerList.isEmpty()) {
} sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
return true;
// Get rid of Planeswalkers, currently only if it can kill them }
// with one touch
CardCollection planeswalkerList = CardLists.filter( // do as M1M1 part
CardLists.filterControlledBy(countersList, ai.getOpponents()), CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
CardPredicates.Presets.PLANEWALKERS,
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount)); CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
if (!planeswalkerList.isEmpty()) { CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList)); if (!aiPersistList.isEmpty()) {
return true; aiM1M1List = aiPersistList;
} }
// do as M1M1 part if (!aiM1M1List.isEmpty()) {
CardCollection aiList = CardLists.filterControlledBy(countersList, ai); sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
return true;
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1)); }
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist"); // do as P1P1 part
if (!aiPersistList.isEmpty()) { CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
aiM1M1List = aiPersistList; CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
}
if (!aiUndyingList.isEmpty()) {
if (!aiM1M1List.isEmpty()) { aiP1P1List = aiUndyingList;
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List)); }
return true; if (!aiP1P1List.isEmpty()) {
} sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
return true;
// do as P1P1 part }
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying"); // fallback to remove any counter from opponent
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents());
if (!aiUndyingList.isEmpty()) { oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
aiP1P1List = aiUndyingList; if (!oppList.isEmpty()) {
} final Card best = ComputerUtilCard.getBestAI(oppList);
if (!aiP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List)); for (final CounterType aType : best.getCounters().keySet()) {
return true; if (!ComputerUtil.isNegativeCounter(aType, best)) {
} sa.getTargets().add(best);
return true;
// fallback to remove any counter from opponent } else if (!ComputerUtil.isUselessCounter(aType)) {
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents()); // whould remove positive counter
oppList = CardLists.filter(oppList, CardPredicates.hasCounters()); if (best.getCounters(aType) <= amount) {
if (!oppList.isEmpty()) { sa.getTargets().add(best);
final Card best = ComputerUtilCard.getBestAI(oppList); return true;
}
for (final CounterType aType : best.getCounters().keySet()) { }
if (!ComputerUtil.isNegativeCounter(aType, best)) { }
sa.getTargets().add(best); }
return true; }
} else if (!ComputerUtil.isUselessCounter(aType)) { }
// whould remove positive counter
if (best.getCounters(aType) <= amount) { if (mandatory) {
sa.getTargets().add(best); sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
return true; return true;
} }
} return false;
} }
}
} @Override
} protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return doTgt(ai, sa, true);
if (mandatory) { }
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
return true; /*
} * (non-Javadoc)
return false; *
} * @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.util.Map)
@Override */
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { @Override
return doTgt(ai, sa, true); public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
}
if (options.size() > 1) {
/* final Player ai = sa.getActivatingPlayer();
* (non-Javadoc) final Game game = ai.getGame();
*
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List, boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
* forge.game.spellability.SpellAbility, java.util.Map)
*/ Card tgt = (Card) params.get("Target");
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) { // planeswalker has high priority for loyalty counters
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
if (options.size() > 1) { return CounterType.LOYALTY;
final Player ai = sa.getActivatingPlayer(); }
final Game game = ai.getGame();
if (tgt.getController().isOpponentOf(ai)) {
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule); // creatures with BaseToughness below or equal zero might be
// killed if their counters are removed
Card tgt = (Card) params.get("Target"); if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
if (options.contains(CounterType.P1P1)) {
// planeswalker has high priority for loyalty counters return CounterType.P1P1;
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) { } else if (options.contains(CounterType.M1M1)) {
return CounterType.LOYALTY; return CounterType.M1M1;
} }
}
if (tgt.getController().isOpponentOf(ai)) {
// creatures with BaseToughness below or equal zero might be // fallback logic, select positive counter to remove it
// killed if their counters are removed for (final CounterType type : options) {
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) { if (!ComputerUtil.isNegativeCounter(type, tgt)) {
if (options.contains(CounterType.P1P1)) { return type;
return CounterType.P1P1; }
} else if (options.contains(CounterType.M1M1)) { }
return CounterType.M1M1; } else {
} // this counters are treat first to be removed
} if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
// fallback logic, select positive counter to remove it return CounterType.ICE;
for (final CounterType type : options) { }
if (!ComputerUtil.isNegativeCounter(type, tgt)) { } else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.P1P1)) {
return type; return CounterType.P1P1;
} } else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.M1M1)) {
} return CounterType.M1M1;
} else { }
// this counters are treat first to be removed
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) { // fallback logic, select positive counter to add more
if (!ai.isCardInPlay("Marit Lage") || noLegendary) { for (final CounterType type : options) {
return CounterType.ICE; if (!ComputerUtil.isNegativeCounter(type, tgt)) {
} return type;
} else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) { }
return CounterType.P1P1; }
} else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) { }
return CounterType.M1M1; }
}
return super.chooseCounterType(options, sa, params);
// fallback logic, select positive counter to add more }
for (final CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, tgt)) { /*
return type; * (non-Javadoc)
} *
} * @see
} * forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
} * BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
*/
return super.chooseCounterType(options, sa, params); @Override
} public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
/* final Player ai = sa.getActivatingPlayer();
* (non-Javadoc) final Game game = ai.getGame();
* Card tgt = (Card) params.get("Target");
* @see CounterType type = (CounterType) params.get("CounterType");
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map) boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
*/
@Override if (tgt.getController().isOpponentOf(ai)) {
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) { if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) { return false;
final Player ai = sa.getActivatingPlayer(); }
final Game game = ai.getGame();
Card tgt = (Card) params.get("Target"); return ComputerUtil.isNegativeCounter(type, tgt);
CounterType type = (CounterType) params.get("CounterType"); } else {
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule); if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return false;
if (tgt.getController().isOpponentOf(ai)) { }
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) { } else if (type.equals(CounterType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
return false; return false;
} } else if (type.equals(CounterType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
return false;
return ComputerUtil.isNegativeCounter(type, tgt); }
} else {
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) { return !ComputerUtil.isNegativeCounter(type, tgt);
if (!ai.isCardInPlay("Marit Lage") || noLegendary) { }
return false; }
} return super.chooseBinary(kindOfChoice, sa, params);
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) { }
return false;
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) { }
return false;
}
return !ComputerUtil.isNegativeCounter(type, tgt);
}
}
return super.chooseBinary(kindOfChoice, sa, params);
}
}

View File

@@ -1,382 +1,375 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import com.google.common.base.Predicates;
import java.util.Map; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtilMana;
import forge.ai.ComputerUtilCard; import forge.ai.SpellAbilityAi;
import forge.ai.ComputerUtilMana; import forge.game.Game;
import forge.ai.SpellAbilityAi; import forge.game.GlobalRuleChange;
import forge.game.Game; import forge.game.ability.AbilityUtils;
import forge.game.GlobalRuleChange; import forge.game.card.*;
import forge.game.ability.AbilityUtils; import forge.game.keyword.Keyword;
import forge.game.card.Card; import forge.game.phase.PhaseHandler;
import forge.game.card.CardCollection; import forge.game.phase.PhaseType;
import forge.game.card.CardCollectionView; import forge.game.player.Player;
import forge.game.card.CardLists; import forge.game.spellability.SpellAbility;
import forge.game.card.CardPredicates; import forge.game.spellability.TargetRestrictions;
import forge.game.card.CounterType; import forge.game.zone.ZoneType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import java.util.List;
import forge.game.player.Player; import java.util.Map;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; public class CountersRemoveAi extends SpellAbilityAi {
import forge.game.zone.ZoneType;
/*
public class CountersRemoveAi extends SpellAbilityAi { * (non-Javadoc)
*
/* * @see
* (non-Javadoc) * forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* * forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
* @see */
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player, @Override
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler) protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
*/ final String type = sa.getParam("CounterType");
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) { if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !type.equals("M1M1")) {
final String type = sa.getParam("CounterType"); return false;
}
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !type.equals("M1M1")) { return super.checkPhaseRestrictions(ai, sa, ph);
return false; }
}
return super.checkPhaseRestrictions(ai, sa, ph); /*
} * (non-Javadoc)
*
/* * @see
* (non-Javadoc) * forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* * forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
* @see * java.lang.String)
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player, */
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler, @Override
* java.lang.String) protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
*/ if ("EndOfOpponentsTurn".equals(logic)) {
@Override if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) {
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) { return false;
if ("EndOfOpponentsTurn".equals(logic)) { }
if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) { }
return false; return super.checkPhaseRestrictions(ai, sa, ph, logic);
} }
}
return super.checkPhaseRestrictions(ai, sa, ph, logic); /*
} * (non-Javadoc)
*
/* * @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* (non-Javadoc) * forge.game.spellability.SpellAbility)
* */
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, @Override
* forge.game.spellability.SpellAbility) protected boolean checkApiLogic(Player ai, SpellAbility sa) {
*/
@Override final String type = sa.getParam("CounterType");
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (sa.usesTargeting()) {
final String type = sa.getParam("CounterType"); return doTgt(ai, sa, false);
}
if (sa.usesTargeting()) {
return doTgt(ai, sa, false); if (!type.matches("Any") && !type.matches("All")) {
} final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
if (currCounters < 1) {
if (!type.matches("Any") && !type.matches("All")) { return false;
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type)); }
if (currCounters < 1) { }
return false;
} return super.checkApiLogic(ai, sa);
} }
return super.checkApiLogic(ai, sa); private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
} final Card source = sa.getHostCard();
final Game game = ai.getGame();
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard(); final String type = sa.getParam("CounterType");
final Game game = ai.getGame(); final String amountStr = sa.getParam("CounterNum");
final String type = sa.getParam("CounterType"); // remove counter with Time might use Exile Zone too
final String amountStr = sa.getParam("CounterNum"); final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
// remove counter with Time might use Exile Zone too // need to targetable
final TargetRestrictions tgt = sa.getTargetRestrictions(); list = CardLists.getTargetableCards(list, sa);
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
// need to targetable if (list.isEmpty()) {
list = CardLists.getTargetableCards(list, sa); return false;
}
if (list.isEmpty()) {
return false; // Filter AI-specific targets if provided
} list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
if (sa.hasParam("AITgts")) { boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
String aiTgts = sa.getParam("AITgts");
CardCollection prefList = CardLists.getValidCards(list, aiTgts.split(","), ai, source, sa); if (type.matches("All")) {
if (!prefList.isEmpty() || sa.hasParam("AITgtsStrict")) { // Logic Part for Vampire Hexmage
list = prefList; // Break Dark Depths
} if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
} CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule); CardPredicates.hasCounter(CounterType.ICE, 3));
if (type.matches("All")) { if (!depthsList.isEmpty()) {
// Logic Part for Vampire Hexmage sa.getTargets().add(depthsList.getFirst());
// Break Dark Depths return true;
if (!ai.isCardInPlay("Marit Lage") || noLegendary) { }
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths"); }
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterType.ICE, 3)); // Get rid of Planeswalkers:
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
if (!depthsList.isEmpty()) { list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
sa.getTargets().add(depthsList.getFirst());
return true; CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS,
} CardPredicates.hasCounter(CounterType.LOYALTY, 5));
}
if (!planeswalkerList.isEmpty()) {
// Get rid of Planeswalkers: sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield); return true;
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa)); }
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS, } else if (type.matches("Any")) {
CardPredicates.hasCounter(CounterType.LOYALTY, 5)); // variable amount for Hex Parasite
int amount;
if (!planeswalkerList.isEmpty()) { boolean xPay = false;
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList)); if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
return true; final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
}
if (manaLeft == 0) {
} else if (type.matches("Any")) { return false;
// variable amount for Hex Parasite }
int amount; amount = manaLeft;
boolean xPay = false; xPay = true;
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) { } else {
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai); amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
if (manaLeft == 0) { // try to remove them from Dark Depths and Planeswalkers too
return false;
} if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
amount = manaLeft; CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
xPay = true; depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
} else { CardPredicates.hasCounter(CounterType.ICE));
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
} if (!depthsList.isEmpty()) {
// try to remove them from Dark Depths and Planeswalkers too Card depth = depthsList.getFirst();
int ice = depth.getCounters(CounterType.ICE);
if (!ai.isCardInPlay("Marit Lage") || noLegendary) { if (amount >= ice) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths"); sa.getTargets().add(depth);
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa), if (xPay) {
CardPredicates.hasCounter(CounterType.ICE)); source.setSVar("PayX", Integer.toString(ice));
}
if (!depthsList.isEmpty()) { return true;
Card depth = depthsList.getFirst(); }
int ice = depth.getCounters(CounterType.ICE); }
if (amount >= ice) { }
sa.getTargets().add(depth);
if (xPay) { // Get rid of Planeswalkers:
source.setSVar("PayX", Integer.toString(ice)); list = game.getPlayers().getCardsIn(ZoneType.Battlefield);
} list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
return true;
} CardCollection planeswalkerList = CardLists.filter(list,
} Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
} CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
// Get rid of Planeswalkers: if (!planeswalkerList.isEmpty()) {
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield); Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa)); sa.getTargets().add(best);
if (xPay) {
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS, source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount)); }
return true;
if (!planeswalkerList.isEmpty()) { }
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
sa.getTargets().add(best); // some rules only for amount = 1
if (xPay) { if (!xPay) {
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty())); // do as M1M1 part
} CardCollection aiList = CardLists.filterControlledBy(list, ai);
return true;
} CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
// some rules only for amount = 1 CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
if (!xPay) { if (!aiPersistList.isEmpty()) {
// do as M1M1 part aiM1M1List = aiPersistList;
CardCollection aiList = CardLists.filterControlledBy(list, ai); }
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1)); if (!aiM1M1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist"); return true;
if (!aiPersistList.isEmpty()) { }
aiM1M1List = aiPersistList;
} // do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
if (!aiM1M1List.isEmpty()) { CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
return true; if (!aiUndyingList.isEmpty()) {
} aiP1P1List = aiUndyingList;
}
// do as P1P1 part if (!aiP1P1List.isEmpty()) {
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1)); sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying"); return true;
}
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList; // fallback to remove any counter from opponent
} CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
if (!aiP1P1List.isEmpty()) { oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List)); if (!oppList.isEmpty()) {
return true; final Card best = ComputerUtilCard.getBestAI(oppList);
}
for (final CounterType aType : best.getCounters().keySet()) {
// fallback to remove any counter from opponent if (!ComputerUtil.isNegativeCounter(aType, best)) {
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents()); sa.getTargets().add(best);
oppList = CardLists.filter(oppList, CardPredicates.hasCounters()); return true;
if (!oppList.isEmpty()) { }
final Card best = ComputerUtilCard.getBestAI(oppList); }
}
for (final CounterType aType : best.getCounters().keySet()) { }
if (!ComputerUtil.isNegativeCounter(aType, best)) { } else if (type.equals("M1M1")) {
sa.getTargets().add(best); // no special amount for that one yet
return true; int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
} CardCollection aiList = CardLists.filterControlledBy(list, ai);
} aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
}
} CardCollection aiPersist = CardLists.getKeyword(aiList, Keyword.PERSIST);
} else if (type.equals("M1M1")) { if (!aiPersist.isEmpty()) {
// no special amount for that one yet aiList = aiPersist;
int amount = AbilityUtils.calculateAmount(source, amountStr, sa); }
CardCollection aiList = CardLists.filterControlledBy(list, ai);
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount)); // TODO do not remove -1/-1 counters from cards which does need
// them for abilities
CardCollection aiPersist = CardLists.getKeyword(aiList, "Persist");
if (!aiPersist.isEmpty()) { if (!aiList.isEmpty()) {
aiList = aiPersist; sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
} return true;
}
// TODO do not remove -1/-1 counters from cards which does need
// them for abilities } else if (type.equals("P1P1")) {
// no special amount for that one yet
if (!aiList.isEmpty()) { int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
return true; list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
}
// currently only logic for Bloodcrazed Hoplite, but add logic for
} else if (type.equals("P1P1")) { // targeting ai creatures too
// no special amount for that one yet CardCollection aiList = CardLists.filterControlledBy(list, ai);
int amount = AbilityUtils.calculateAmount(source, amountStr, sa); if (!aiList.isEmpty()) {
CardCollection aiListUndying = CardLists.getKeyword(aiList, Keyword.UNDYING);
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount)); if (!aiListUndying.isEmpty()) {
aiList = aiListUndying;
// currently only logic for Bloodcrazed Hoplite, but add logic for }
// targeting ai creatures too if (!aiList.isEmpty()) {
CardCollection aiList = CardLists.filterControlledBy(list, ai); sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
if (!aiList.isEmpty()) { return true;
CardCollection aiListUndying = CardLists.getKeyword(aiList, "Undying"); }
if (!aiListUndying.isEmpty()) { }
aiList = aiListUndying;
} // need to target opponent creatures
if (!aiList.isEmpty()) { CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList)); if (!oppList.isEmpty()) {
return true; CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, Keyword.UNDYING);
} if (!oppListNotUndying.isEmpty()) {
} oppList = oppListNotUndying;
}
// need to target opponent creatures
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents()); if (!oppList.isEmpty()) {
if (!oppList.isEmpty()) { sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, "Undying"); return true;
if (!oppListNotUndying.isEmpty()) { }
oppList = oppListNotUndying; }
}
} else if (type.equals("TIME")) {
if (!oppList.isEmpty()) { int amount;
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList)); boolean xPay = false;
return true; // Timecrafting has X R
} if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
} final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
} else if (type.equals("TIME")) { if (manaLeft == 0) {
int amount; return false;
boolean xPay = false; }
// Timecrafting has X R amount = manaLeft;
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) { xPay = true;
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai); } else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
if (manaLeft == 0) { }
return false;
} CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
amount = manaLeft;
xPay = true; if (!timeList.isEmpty()) {
} else { Card best = ComputerUtilCard.getBestAI(timeList);
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
} int timeCount = best.getCounters(CounterType.TIME);
sa.getTargets().add(best);
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount)); if (xPay) {
source.setSVar("PayX", Integer.toString(timeCount));
if (!timeList.isEmpty()) { }
Card best = ComputerUtilCard.getBestAI(timeList); return true;
}
int timeCount = best.getCounters(CounterType.TIME); }
sa.getTargets().add(best); if (mandatory) {
if (xPay) { sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
source.setSVar("PayX", Integer.toString(timeCount)); return true;
} }
return true; return false;
} }
}
if (mandatory) { @Override
sa.getTargets().add(ComputerUtilCard.getWorstAI(list)); protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true; if (sa.usesTargeting()) {
} return doTgt(aiPlayer, sa, mandatory);
return false; }
} return mandatory;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { /*
if (sa.usesTargeting()) { * (non-Javadoc)
return doTgt(aiPlayer, sa, mandatory); *
} * @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
return mandatory; * forge.game.spellability.SpellAbility, int, int, java.util.Map)
} */
@Override
/* public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
* (non-Javadoc) // TODO Auto-generated method stub
* return super.chooseNumber(player, sa, min, max, params);
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player, }
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
*/ /*
@Override * (non-Javadoc)
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) { *
// TODO Auto-generated method stub * @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
return super.chooseNumber(player, sa, min, max, params); * forge.game.spellability.SpellAbility, java.util.Map)
} */
@Override
/* public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
* (non-Javadoc) if (options.size() <= 1) {
* return super.chooseCounterType(options, sa, params);
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List, }
* forge.game.spellability.SpellAbility, java.util.Map) Player ai = sa.getActivatingPlayer();
*/ Card target = (Card) params.get("Target");
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) { if (target.getController().isOpponentOf(ai)) {
if (options.size() <= 1) { // if its a Planeswalker try to remove Loyality first
return super.chooseCounterType(options, sa, params); if (target.isPlaneswalker()) {
} return CounterType.LOYALTY;
Player ai = sa.getActivatingPlayer(); }
Card target = (Card) params.get("Target"); for (CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, target)) {
if (target.getController().isOpponentOf(ai)) { return type;
// if its a Planeswalker try to remove Loyality first }
if (target.isPlaneswalker()) { }
return CounterType.LOYALTY; } else {
} if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) {
for (CounterType type : options) { return CounterType.M1M1;
if (!ComputerUtil.isNegativeCounter(type, target)) { } else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
return type; return CounterType.M1M1;
} }
} for (CounterType type : options) {
} else { if (ComputerUtil.isNegativeCounter(type, target)) {
if (options.contains(CounterType.M1M1) && target.hasKeyword("Persist")) { return type;
return CounterType.M1M1; }
} else if (options.contains(CounterType.P1P1) && target.hasKeyword("Undying")) { }
return CounterType.M1M1; }
} return super.chooseCounterType(options, sa, params);
for (CounterType type : options) { }
if (ComputerUtil.isNegativeCounter(type, target)) { }
return type;
}
}
}
return super.chooseCounterType(options, sa, params);
}
}

View File

@@ -1,93 +1,168 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.card.CardCollectionView; import forge.game.card.Card;
import forge.game.card.CardPredicates; import forge.game.card.CardCollectionView;
import forge.game.phase.PhaseHandler; import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.phase.PhaseHandler;
import forge.game.spellability.SpellAbility; import forge.game.phase.PhaseType;
import forge.game.zone.ZoneType; import forge.game.player.Player;
import forge.util.MyRandom; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
public abstract class DamageAiBase extends SpellAbilityAi { import forge.game.trigger.TriggerDamageDone;
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) { import forge.game.zone.ZoneType;
int restDamage = d; import forge.util.MyRandom;
final Game game = comp.getGame();
final Player enemy = comp.getOpponent(); public abstract class DamageAiBase extends SpellAbilityAi {
if (!sa.canTarget(enemy)) { protected boolean avoidTargetP(final Player comp, final SpellAbility sa) {
return false; Player enemy = ComputerUtil.getOpponentFor(comp);
} // Logic for cards that damage owner, like Fireslinger
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) { // Do not target a player if they aren't below 75% of our health.
return false; // Unless Lifelink will cancel the damage to us
} Card hostcard = sa.getHostCard();
boolean lifelink = hostcard.hasKeyword(Keyword.LIFELINK);
// burn Planeswalkers if (!lifelink) {
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) { for (Card ench : hostcard.getEnchantedBy(false)) {
return true; // Treat cards enchanted by older cards with "when enchanted creature deals damage, gain life" as if they had lifelink.
} if (ench.hasSVar("LikeLifeLink")) {
if ("True".equals(ench.getSVar("LikeLifeLink"))) {
if (!noPrevention) { lifelink = true;
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false); }
} else { }
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false); }
} }
if ("SelfDamage".equals(sa.getParam("AILogic"))) {
if (restDamage == 0) { if (comp.getLife() * 0.75 < enemy.getLife()) {
return false; if (!lifelink) {
} return true;
}
if (!enemy.canLoseLife()) { }
return false; }
} return false;
}
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
if ((enemy.getLife() - restDamage) < 5) { // TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and
// drop the human to less than 5 // remove the "burn Planeswalkers" code in the called method.
// life return shouldTgtP(comp, sa, d, noPrevention, false);
return true; }
} protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
int restDamage = d;
if (sa.isSpell()) { final Game game = comp.getGame();
PhaseHandler phase = game.getPhaseHandler(); Player enemy = ComputerUtil.getOpponentFor(comp);
// If this is a spell, cast it instead of discarding boolean dmgByCardsInHand = false;
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
&& phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) { if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
return true; sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
} dmgByCardsInHand = true;
}
// chance to burn player based on current hand size // Not sure if type choice implemented for the AI yet but it should at least recognize this spell hits harder on larger enemy hand size
if (hand.size() > 2) { if ("Blood Oath".equals(sa.getHostCard().getName())) {
float value = 0; dmgByCardsInHand = true;
if (SpellAbilityAi.isSorcerySpeed(sa)) { }
//lower chance for sorcery as other spells may be cast in main2
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) { if (!sa.canTarget(enemy)) {
value = 1.0f * restDamage / enemy.getLife(); return false;
} }
} else { if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
if (phase.isPlayerTurn(enemy) && phase.is(PhaseType.END_OF_TURN)) { return false;
value = 1.5f * restDamage / enemy.getLife(); }
}
} // Benefits hitting players?
if (value > 0) { //more likely to burn with larger hand // If has triggered ability on dealing damage to an opponent, go for it!
for (int i = 3; i < hand.size(); i++) { Card hostcard = sa.getHostCard();
value *= 1.1f; for (Trigger trig : hostcard.getTriggers()) {
} if (trig instanceof TriggerDamageDone) {
} if (("Opponent".equals(trig.getParam("ValidTarget")))
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time && (!"True".equals(trig.getParam("CombatDamage")))) {
return false; return true;
} else { }
final float chance = MyRandom.getRandom().nextFloat(); }
return chance < value; }
}
} // burn Planeswalkers
} // TODO: Must be removed completely when the "planeswalker redirection" rule is removed.
if (!noPlaneswalkerRedirection
return false; && Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) {
} return true;
} }
if (avoidTargetP(comp, sa)) {
return false;
}
if (!noPrevention) {
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
} else {
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
}
if (restDamage == 0) {
return false;
}
if (!enemy.canLoseLife()) {
return false;
}
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
if ((enemy.getLife() - restDamage) < 5) {
// drop the human to less than 5
// life
return true;
}
if (sa.isSpell()) {
PhaseHandler phase = game.getPhaseHandler();
// If this is a spell, cast it instead of discarding
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
&& phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
return true;
}
// chance to burn player based on current hand size
if (hand.size() > 2) {
float value = 0;
if (SpellAbilityAi.isSorcerySpeed(sa)) {
//lower chance for sorcery as other spells may be cast in main2
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
value = 1.0f * restDamage / enemy.getLife();
}
} else {
// If Sudden Impact type spell, and can hit at least 3 cards during draw phase
// have a 100% chance to go for it, enemy hand will only lose cards over time!
// But if 3 or less cards, use normal rules, just in case enemy starts holding card or plays a draw spell or we need mana for other instants.
if (phase.isPlayerTurn(enemy)) {
if (dmgByCardsInHand
&& (phase.is(PhaseType.DRAW))
&& (enemy.getCardsIn(ZoneType.Hand).size() > 3)) {
value = 1;
} else if (phase.is(PhaseType.END_OF_TURN)
|| ((dmgByCardsInHand && phase.getPhase().isAfter(PhaseType.UPKEEP)))) {
value = 1.5f * restDamage / enemy.getLife();
}
}
}
if (value > 0) { //more likely to burn with larger hand
for (int i = 3; i < hand.size(); i++) {
value *= 1.1f;
}
}
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
return false;
} else {
final float chance = MyRandom.getRandom().nextFloat();
return chance < value;
}
}
}
return false;
}
}

View File

@@ -1,297 +1,330 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.*;
import forge.ai.*; import forge.game.ability.AbilityUtils;
import forge.game.ability.AbilityUtils; import forge.game.card.Card;
import forge.game.card.Card; import forge.game.card.CardCollection;
import forge.game.card.CardCollection; import forge.game.card.CardLists;
import forge.game.card.CardLists; import forge.game.card.CounterType;
import forge.game.card.CounterType; import forge.game.cost.Cost;
import forge.game.cost.Cost; import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import java.util.Random; public class DamageAllAi extends SpellAbilityAi {
@Override
public class DamageAllAi extends SpellAbilityAi { protected boolean canPlayAI(Player ai, SpellAbility sa) {
@Override // AI needs to be expanded, since this function can be pretty complex
protected boolean canPlayAI(Player ai, SpellAbility sa) { // based on what the expected targets could be
// AI needs to be expanded, since this function can be pretty complex final Card source = sa.getHostCard();
// based on what the expected targets could be
final Card source = sa.getHostCard(); // prevent run-away activations - first time will always return true
if (MyRandom.getRandom().nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
// prevent run-away activations - first time will always return true return false;
final Random r = MyRandom.getRandom(); }
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) { // abCost stuff that should probably be centralized...
return false; final Cost abCost = sa.getPayCosts();
} if (abCost != null) {
// abCost stuff that should probably be centralized... // AI currently disabled for some costs
final Cost abCost = sa.getPayCosts(); if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
if (abCost != null) { return false;
// AI currently disabled for some costs }
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) { }
return false; // wait until stack is empty (prevents duplicate kills)
} if (!ai.getGame().getStack().isEmpty()) {
} return false;
// wait until stack is empty (prevents duplicate kills) }
if (!ai.getGame().getStack().isEmpty()) {
return false; int x = -1;
} final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
int x = -1; if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
final String damage = sa.getParam("NumDmg"); dmg = ComputerUtilMana.getConvergeCount(sa, ai);
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); }
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
dmg = ComputerUtilMana.getConvergeCount(sa, ai); x = ComputerUtilMana.determineLeftoverMana(sa, ai);
} }
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { if (damage.equals("ChosenX")) {
x = ComputerUtilMana.determineLeftoverMana(sa, ai); x = source.getCounters(CounterType.LOYALTY);
} }
if (damage.equals("ChosenX")) { if (x == -1) {
x = source.getCounters(CounterType.LOYALTY); Player bestOpp = determineOppToKill(ai, sa, source, dmg);
} if (determineOppToKill(ai, sa, source, dmg) != null) {
if (x == -1) { // we already know we can kill a player, so go for it
return evaluateDamageAll(ai, sa, source, dmg) > 0; return true;
} else { }
int best = -1, best_x = -1; // look for other value in this (damaging creatures or
for (int i = 0; i < x; i++) { // creatures + player, e.g. Pestilence, etc.)
final int value = evaluateDamageAll(ai, sa, source, i); return evaluateDamageAll(ai, sa, source, dmg) > 0;
if (value > best) { } else {
best = value; int best = -1, best_x = -1;
best_x = i; Player bestOpp = determineOppToKill(ai, sa, source, x);
} if (bestOpp != null) {
} // we can finish off a player, so go for it
if (best_x > 0) {
if (sa.getSVar(damage).equals("Count$xPaid")) { // TODO: improve this by possibly damaging more creatures
source.setSVar("PayX", Integer.toString(best_x)); // on the battlefield belonging to other opponents at the same
} // time, if viable
if (damage.equals("ChosenX")) { best_x = bestOpp.getLife();
source.setSVar("ChosenX", "Number$" + best_x); } else {
} // see if it's possible to get value from killing off creatures
return true; for (int i = 0; i <= x; i++) {
} final int value = evaluateDamageAll(ai, sa, source, i);
return false; if (value > best) {
} best = value;
} best_x = i;
}
private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) { }
Player opp = ai.getOpponent(); }
final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg); if (best_x > 0) {
if (sa.getSVar(damage).equals("Count$xPaid")) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); source.setSVar("PayX", Integer.toString(best_x));
if (tgt != null && sa.canTarget(opp)) { }
sa.resetTargets(); if (damage.equals("ChosenX")) {
sa.getTargets().add(opp); source.setSVar("ChosenX", "Number$" + best_x);
computerList.clear(); }
} return true;
}
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : ""; return false;
// TODO: if damage is dependant on mana paid, maybe have X be human's max life }
// Don't kill yourself }
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return -1; private Player determineOppToKill(Player ai, SpellAbility sa, Card source, int x) {
} // Attempt to determine which opponent can be finished off such that the most players
// are killed at the same time, given X damage tops
// if we can kill human, do it final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
if ((validP.equals("Player") || validP.contains("Opponent")) int aiLife = ai.getLife();
&& (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) { Player bestOpp = null; // default opponent, if all else fails
return 1;
} for (int dmg = 1; dmg <= x; dmg++) {
// Don't kill yourself in the process
int minGain = 200; // The minimum gain in destroyed creatures if (validP.equals("Player") && aiLife <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)) {
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) { break;
if (computerList.isEmpty()) { }
minGain = 10; // nothing to lose for (Player opp : ai.getOpponents()) {
// no creatures to lose and player can be damaged if ((validP.equals("Player") || validP.contains("Opponent"))
// so do it if it's helping! && (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
// ---------------------------- bestOpp = opp;
// needs future improvement on pestilence : }
// what if we lose creatures but can win by repeated activations? }
// that tactic only works if there are creatures left to keep pestilence in play }
// and can kill the player in a reasonable amount of time (no more than 2-3 turns?)
if (validP.equals("Player")) { return bestOpp;
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) { }
// When using Pestilence to hurt players, do it at
// the end of the opponent's turn only private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) {
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic"))) final Player opp = ai.getWeakestOpponent();
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
&& (ai.getGame().getNonactivePlayers().contains(ai))))) CardCollection computerList = getKillableCreatures(sa, ai, dmg);
// Need further improvement : if able to kill immediately with repeated activations, do not wait
// for phases! Will also need to implement considering repeated activations for killed creatures! final TargetRestrictions tgt = sa.getTargetRestrictions();
// || (ai.sa.getPayCosts(). ??? ) if (tgt != null && sa.canTarget(opp)) {
{ sa.resetTargets();
// would take zero damage, and hurt opponent, do it! sa.getTargets().add(opp);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)<1) { computerList.clear();
return 1; }
}
// enemy is expected to die faster than AI from damage if repeated final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
if (ai.getLife() > ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) // TODO: if damage is dependant on mana paid, maybe have X be human's max life
* ((opp.getLife() + ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) - 1) // Don't kill yourself
/ ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) { if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
// enemy below 10 life, go for it! return -1;
if ((opp.getLife() < 10) }
&& (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) >= 1)) {
return 1; int minGain = 200; // The minimum gain in destroyed creatures
} if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
// At least half enemy remaining life can be removed in one go if (computerList.isEmpty()) {
// worth doing even if enemy still has high health - one more copy of spell to win! minGain = 10; // nothing to lose
if (opp.getLife() <= 2 * ComputerUtilCombat.predictDamageTo(opp, dmg, source, false)) { // no creatures to lose and player can be damaged
return 1; // so do it if it's helping!
} // ----------------------------
} // needs future improvement on pestilence :
} // what if we lose creatures but can win by repeated activations?
} // that tactic only works if there are creatures left to keep pestilence in play
} // and can kill the player in a reasonable amount of time (no more than 2-3 turns?)
} else { if (validP.equals("Player")) {
minGain = 100; // safety for errors in evaluate creature if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
} // When using Pestilence to hurt players, do it at
} else if (sa.getSubAbility() != null && ai.getGame().getPhaseHandler().isPreCombatMain() && computerList.isEmpty() // the end of the opponent's turn only
&& opp.getCreaturesInPlay().size() > 1 && !ai.getCreaturesInPlay().isEmpty()) { if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
minGain = 126; // prepare for attack || ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
} && (ai.getGame().getNonactivePlayers().contains(ai)))))
// Need further improvement : if able to kill immediately with repeated activations, do not wait
return ComputerUtilCard.evaluateCreatureList(humanList) - ComputerUtilCard.evaluateCreatureList(computerList) // for phases! Will also need to implement considering repeated activations for killed creatures!
- minGain; // || (ai.sa.getPayCosts(). ??? )
} {
// would take zero damage, and hurt opponent, do it!
@Override if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)<1) {
public boolean chkAIDrawback(SpellAbility sa, Player ai) { return 1;
final Card source = sa.getHostCard(); }
String validP = ""; // enemy is expected to die faster than AI from damage if repeated
if (ai.getLife() > ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)
final String damage = sa.getParam("NumDmg"); * ((opp.getLife() + ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) - 1)
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); / ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
// enemy below 10 life, go for it!
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { if ((opp.getLife() < 10)
// Set PayX here to maximum value. && (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) >= 1)) {
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); return 1;
source.setSVar("PayX", Integer.toString(dmg)); }
} // At least half enemy remaining life can be removed in one go
// worth doing even if enemy still has high health - one more copy of spell to win!
if (sa.hasParam("ValidPlayers")) { if (opp.getLife() <= 2 * ComputerUtilCombat.predictDamageTo(opp, dmg, source, false)) {
validP = sa.getParam("ValidPlayers"); return 1;
} }
}
// Evaluate creatures getting killed }
Player enemy = ai.getOpponent(); }
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg); }
CardCollection computerList = getKillableCreatures(sa, ai, dmg); } else {
final TargetRestrictions tgt = sa.getTargetRestrictions(); minGain = 100; // safety for errors in evaluate creature
}
if (tgt != null && sa.canTarget(enemy)) { } else if (sa.getSubAbility() != null && ai.getGame().getPhaseHandler().isPreCombatMain() && computerList.isEmpty()
sa.resetTargets(); && opp.getCreaturesInPlay().size() > 1 && !ai.getCreaturesInPlay().isEmpty()) {
sa.getTargets().add(enemy); minGain = 126; // prepare for attack
computerList.clear(); }
}
// Don't get yourself killed return ComputerUtilCard.evaluateCreatureList(humanList) - ComputerUtilCard.evaluateCreatureList(computerList)
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) { - minGain;
return false; }
}
@Override
// if we can kill human, do it public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted")) final Card source = sa.getHostCard();
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) { String validP = "";
return true;
} final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
.evaluateCreatureList(humanList)) { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
return false; // Set PayX here to maximum value.
} dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
return true; }
}
if (sa.hasParam("ValidPlayers")) {
/** validP = sa.getParam("ValidPlayers");
* <p> }
* getKillableCreatures.
* </p> // Evaluate creatures getting killed
* Player enemy = ComputerUtil.getOpponentFor(ai);
* @param sa final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
* a {@link forge.game.spellability.SpellAbility} object. CardCollection computerList = getKillableCreatures(sa, ai, dmg);
* @param player final TargetRestrictions tgt = sa.getTargetRestrictions();
* a {@link forge.game.player.Player} object.
* @param dmg if (tgt != null && sa.canTarget(enemy)) {
* a int. sa.resetTargets();
* @return a {@link forge.game.card.CardCollection} object. sa.getTargets().add(enemy);
*/ computerList.clear();
private CardCollection getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) { }
final Card source = sa.getHostCard(); // Don't get yourself killed
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : ""; if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
// TODO: X may be something different than X paid }
CardCollection list =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa); // if we can kill human, do it
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
final Predicate<Card> filterKillable = new Predicate<Card>() { && (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
@Override return true;
public boolean apply(final Card c) { }
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
} if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
}; .evaluateCreatureList(humanList)) {
return false;
list = CardLists.getNotKeyword(list, "Indestructible"); }
list = CardLists.filter(list, filterKillable);
return true;
return list; }
}
/**
@Override * <p>
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { * getKillableCreatures.
final Card source = sa.getHostCard(); * </p>
String validP = ""; *
* @param sa
final String damage = sa.getParam("NumDmg"); * a {@link forge.game.spellability.SpellAbility} object.
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); * @param player
* a {@link forge.game.player.Player} object.
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { * @param dmg
// Set PayX here to maximum value. * a int.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); * @return a {@link forge.game.card.CardCollection} object.
source.setSVar("PayX", Integer.toString(dmg)); */
} private CardCollection getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
final Card source = sa.getHostCard();
if (sa.hasParam("ValidPlayers")) { String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
validP = sa.getParam("ValidPlayers");
} // TODO: X may be something different than X paid
CardCollection list =
// Evaluate creatures getting killed CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa);
Player enemy = ai.getOpponent();
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg); final Predicate<Card> filterKillable = new Predicate<Card>() {
CardCollection computerList = getKillableCreatures(sa, ai, dmg); @Override
final TargetRestrictions tgt = sa.getTargetRestrictions(); public boolean apply(final Card c) {
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
if (tgt != null && sa.canTarget(enemy)) { }
sa.resetTargets(); };
sa.getTargets().add(enemy);
computerList.clear(); list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
} list = CardLists.filter(list, filterKillable);
// If it's not mandatory check a few things return list;
if (mandatory) { }
return true;
} @Override
// Don't get yourself killed protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) { final Card source = sa.getHostCard();
return false; String validP = "";
}
final String damage = sa.getParam("NumDmg");
// if we can kill human, do it int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
return true; // Set PayX here to maximum value.
} dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard }
.evaluateCreatureList(humanList)) {
return false; if (sa.hasParam("ValidPlayers")) {
} validP = sa.getParam("ValidPlayers");
}
return true;
} // Evaluate creatures getting killed
} Player enemy = ComputerUtil.getOpponentFor(ai);
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}
// If it's not mandatory check a few things
if (mandatory) {
return true;
}
// Don't get yourself killed
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
}
// if we can kill human, do it
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
return true;
}
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
.evaluateCreatureList(humanList)) {
return false;
}
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +1,54 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpecialCardAi; import forge.ai.SpecialCardAi;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.player.Player;
import forge.game.card.CardCollection; import forge.game.player.PlayerCollection;
import forge.game.card.CardLists; import forge.game.player.PlayerPredicates;
import forge.game.card.CardPredicates; import forge.game.spellability.SpellAbility;
import forge.game.player.Player; import forge.game.spellability.TargetRestrictions;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates; public class DamageEachAi extends DamageAiBase {
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
public class DamageEachAi extends DamageAiBase { */
@Override
/* (non-Javadoc) protected boolean canPlayAI(Player ai, SpellAbility sa) {
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) final TargetRestrictions tgt = sa.getTargetRestrictions();
*/ final String logic = sa.getParam("AILogic");
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
final TargetRestrictions tgt = sa.getTargetRestrictions(); Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
final String logic = sa.getParam("AILogic");
if (tgt != null && weakestOpp != null) {
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa)); sa.resetTargets();
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife()); sa.getTargets().add(weakestOpp);
}
if (tgt != null && weakestOpp != null) {
sa.resetTargets(); if ("MadSarkhanUltimate".equals(logic)) {
sa.getTargets().add(weakestOpp); return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
} }
if ("MadSarkhanUltimate".equals(logic)) { final String damage = sa.getParam("NumDmg");
return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp); final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
} return this.shouldTgtP(ai, sa, iDmg, false);
}
final String damage = sa.getParam("NumDmg");
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa); @Override
return this.shouldTgtP(ai, sa, iDmg, false); public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
} // check AI life before playing this drawback?
return true;
@Override }
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// check AI life before playing this drawback? /* (non-Javadoc)
return true; * @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
} */
@Override
/* (non-Javadoc) protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/ return canPlayAI(ai, sa);
@Override }
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
}
return canPlayAI(ai, sa);
}
}

View File

@@ -1,224 +1,224 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.*; import forge.ai.*;
import forge.game.Game; import forge.game.Game;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class DamagePreventAi extends SpellAbilityAi { public class DamagePreventAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat(); final Combat combat = game.getCombat();
boolean chance = false; boolean chance = false;
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI // temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) { if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false; return false;
} }
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) { if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false; return false;
} }
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) { if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false; return false;
} }
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) { if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false; return false;
} }
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) { if (tgt == null) {
// As far as I can tell these Defined Cards will only have one of // As far as I can tell these Defined Cards will only have one of
// them // them
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa); final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
// react to threats on the stack // react to threats on the stack
if (!game.getStack().isEmpty()) { if (!game.getStack().isEmpty()) {
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Object o : objects) { for (final Object o : objects) {
if (threatenedObjects.contains(o)) { if (threatenedObjects.contains(o)) {
chance = true; chance = true;
} }
} }
} else { } else {
PhaseHandler handler = game.getPhaseHandler(); PhaseHandler handler = game.getPhaseHandler();
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
boolean flag = false; boolean flag = false;
for (final Object o : objects) { for (final Object o : objects) {
if (o instanceof Card) { if (o instanceof Card) {
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat); flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
} else if (o instanceof Player) { } else if (o instanceof Player) {
// Don't need to worry about Combat Damage during AI's turn // Don't need to worry about Combat Damage during AI's turn
final Player p = (Player) o; final Player p = (Player) o;
if (!handler.isPlayerTurn(p)) { if (!handler.isPlayerTurn(p)) {
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat))); .isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
} }
} }
} }
chance = flag; chance = flag;
} else { // if nothing on the stack, and it's not declare } else { // if nothing on the stack, and it's not declare
// blockers. no need to prevent // blockers. no need to prevent
return false; return false;
} }
} }
} // non-targeted } // non-targeted
// react to threats on the stack // react to threats on the stack
else if (!game.getStack().isEmpty()) { else if (!game.getStack().isEmpty()) {
sa.resetTargets(); sa.resetTargets();
final TargetChoices tcs = sa.getTargets(); final TargetChoices tcs = sa.getTargets();
// check stack for something on the stack will kill anything i control // check stack for something on the stack will kill anything i control
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa); final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
if (objects.contains(ai)) { if (objects.contains(ai)) {
tcs.add(ai); tcs.add(ai);
chance = true; chance = true;
} }
final List<Card> threatenedTargets = new ArrayList<Card>(); final List<Card> threatenedTargets = new ArrayList<Card>();
// filter AIs battlefield by what I can target // filter AIs battlefield by what I can target
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa); List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
targetables = CardLists.getTargetableCards(targetables, sa); targetables = CardLists.getTargetableCards(targetables, sa);
for (final Card c : targetables) { for (final Card c : targetables) {
if (objects.contains(c)) { if (objects.contains(c)) {
threatenedTargets.add(c); threatenedTargets.add(c);
} }
} }
if (!threatenedTargets.isEmpty()) { if (!threatenedTargets.isEmpty()) {
// Choose "best" of the remaining to save // Choose "best" of the remaining to save
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets)); tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
chance = true; chance = true;
} }
} // Protect combatants } // Protect combatants
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
sa.resetTargets(); sa.resetTargets();
final TargetChoices tcs = sa.getTargets(); final TargetChoices tcs = sa.getTargets();
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat) if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger()) && (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) { && game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
tcs.add(ai); tcs.add(ai);
chance = true; chance = true;
} else { } else {
// filter AIs battlefield by what I can target // filter AIs battlefield by what I can target
CardCollectionView targetables = ai.getCardsIn(ZoneType.Battlefield); CardCollectionView targetables = ai.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard, sa); targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard, sa);
targetables = CardLists.getTargetableCards(targetables, sa); targetables = CardLists.getTargetableCards(targetables, sa);
if (targetables.isEmpty()) { if (targetables.isEmpty()) {
return false; return false;
} }
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES); final CardCollection combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
ComputerUtilCard.sortByEvaluateCreature(combatants); ComputerUtilCard.sortByEvaluateCreature(combatants);
for (final Card c : combatants) { for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) { if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
tcs.add(c); tcs.add(c);
chance = true; chance = true;
} }
} }
} }
} }
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) { if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa)); tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
} }
return chance; return chance;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
boolean chance = false; boolean chance = false;
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) { if (tgt == null) {
// If there's no target on the trigger, just say yes. // If there's no target on the trigger, just say yes.
chance = true; chance = true;
} else { } else {
chance = preventDamageMandatoryTarget(ai, sa, mandatory); chance = preventDamageMandatoryTarget(ai, sa, mandatory);
} }
return chance; return chance;
} }
/** /**
* <p> * <p>
* preventDamageMandatoryTarget. * preventDamageMandatoryTarget.
* </p> * </p>
* *
* @param sa * @param sa
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory * @param mandatory
* a boolean. * a boolean.
* @return a boolean. * @return a boolean.
*/ */
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) { private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets(); sa.resetTargets();
// filter AIs battlefield by what I can target // filter AIs battlefield by what I can target
final Game game = ai.getGame(); final Game game = ai.getGame();
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield); CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa); targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai); final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
Card target = null; Card target = null;
if (targetables.isEmpty()) { if (targetables.isEmpty()) {
return false; return false;
} }
if (!mandatory && compTargetables.isEmpty()) { if (!mandatory && compTargetables.isEmpty()) {
return false; return false;
} }
if (!compTargetables.isEmpty()) { if (!compTargetables.isEmpty()) {
final CardCollection combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES); final CardCollection combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
ComputerUtilCard.sortByEvaluateCreature(combatants); ComputerUtilCard.sortByEvaluateCreature(combatants);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat(); Combat combat = game.getCombat();
for (final Card c : combatants) { for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
target = c; target = c;
break; break;
} }
} }
} }
if (target == null) { if (target == null) {
target = combatants.get(0); target = combatants.get(0);
} }
} else { } else {
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true); target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
} }
sa.getTargets().add(target); sa.getTargets().add(target);
if (sa.hasParam("DividedAsYouChoose")) { if (sa.hasParam("DividedAsYouChoose")) {
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa)); tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
} }
return true; return true;
} }
} }

View File

@@ -1,59 +1,59 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class DamagePreventAllAi extends SpellAbilityAi { public class DamagePreventAllAi extends SpellAbilityAi {
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard(); final Card hostCard = sa.getHostCard();
boolean chance = false; boolean chance = false;
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI // temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) { if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false; return false;
} }
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) { if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false; return false;
} }
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) { if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false; return false;
} }
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) { if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false; return false;
} }
if (!ai.getGame().getStack().isEmpty()) { if (!ai.getGame().getStack().isEmpty()) {
// TODO check stack for something on the stack will kill anything i // TODO check stack for something on the stack will kill anything i
// control // control
} // Protect combatants } // Protect combatants
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
// TODO // TODO
} }
return chance; return chance;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true; boolean chance = true;
return chance; return chance;
} }
} }

View File

@@ -1,282 +1,283 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtilCost;
import forge.game.Game; import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils; import forge.game.Game;
import forge.game.card.Card; import forge.game.ability.AbilityUtils;
import forge.game.card.CardCollection; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardCollection;
import forge.game.combat.Combat; import forge.game.card.CardLists;
import forge.game.cost.Cost; import forge.game.combat.Combat;
import forge.game.phase.PhaseHandler; import forge.game.cost.Cost;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseHandler;
import forge.game.player.Player; import forge.game.phase.PhaseType;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.Arrays; import java.util.ArrayList;
import java.util.List; import java.util.Arrays;
import java.util.List;
public class DebuffAi extends SpellAbilityAi {
// ************************************************************************* public class DebuffAi extends SpellAbilityAi {
// ***************************** Debuff ************************************ // *************************************************************************
// ************************************************************************* // ***************************** Debuff ************************************
// *************************************************************************
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) { @Override
// if there is no target and host card isn't in play, don't activate protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard(); // if there is no target and host card isn't in play, don't activate
final Game game = ai.getGame(); final Card source = sa.getHostCard();
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) { final Game game = ai.getGame();
return false; if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
} return false;
}
final Cost cost = sa.getPayCosts();
final Cost cost = sa.getPayCosts();
// temporarily disabled until AI is improved
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source)) { // temporarily disabled until AI is improved
return false; if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
} return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
return false; if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
} return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
return false; if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
} return false;
}
final PhaseHandler ph = game.getPhaseHandler();
final PhaseHandler ph = game.getPhaseHandler();
// Phase Restrictions
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) // Phase Restrictions
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| !game.getStack().isEmpty()) { || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
// Instant-speed pumps should not be cast outside of combat when the || !game.getStack().isEmpty()) {
// stack is empty // Instant-speed pumps should not be cast outside of combat when the
if (!SpellAbilityAi.isSorcerySpeed(sa)) { // stack is empty
return false; if (!SpellAbilityAi.isSorcerySpeed(sa)) {
} return false;
} }
}
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
final Combat combat = game.getCombat();
return Iterables.any(cards, new Predicate<Card>() { final Combat combat = game.getCombat();
@Override return Iterables.any(cards, new Predicate<Card>() {
public boolean apply(final Card c) { @Override
public boolean apply(final Card c) {
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
return false; if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
return false;
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
return false; if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
} return false;
// don't add duplicate negative keywords }
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & "))); // don't add duplicate negative keywords
} return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
}); }
} else { });
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false); } else {
} return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
} }
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { @Override
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
// here? // TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
} else { // here?
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false); } else {
} return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
return true;
} // debuffDrawbackAI() return true;
} // debuffDrawbackAI()
/**
* <p> /**
* debuffTgtAI. * <p>
* </p> * debuffTgtAI.
* * </p>
* @param sa *
* a {@link forge.game.spellability.SpellAbility} object. * @param sa
* @param kws * a {@link forge.game.spellability.SpellAbility} object.
* a {@link java.util.ArrayList} object. * @param kws
* @param mandatory * a {@link java.util.ArrayList} object.
* a boolean. * @param mandatory
* @return a boolean. * a boolean.
*/ * @return a boolean.
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) { */
// this would be for evasive things like Flying, Unblockable, etc private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) { // this would be for evasive things like Flying, Unblockable, etc
return false; if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
} return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets(); final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws); sa.resetTargets();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa); CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
// several uses here:
// 1. make human creatures lose evasion when they are attacking // several uses here:
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when // 1. make human creatures lose evasion when they are attacking
// Comp is attacking // 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
// 3. remove Indestructible keyword so it can be destroyed? // Comp is attacking
// 3a. remove Persist? // 3. remove Indestructible keyword so it can be destroyed?
// 3a. remove Persist?
if (list.isEmpty()) {
return mandatory && debuffMandatoryTarget(ai, sa, mandatory); if (list.isEmpty()) {
} return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null; while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
// boolean goodt = false; Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) { if (list.isEmpty()) {
if (mandatory) { if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
return debuffMandatoryTarget(ai, sa, mandatory); if (mandatory) {
} return debuffMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false; sa.resetTargets();
} else { return false;
// TODO is this good enough? for up to amounts? } else {
break; // TODO is this good enough? for up to amounts?
} break;
} }
}
t = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(t); t = ComputerUtilCard.getBestCreatureAI(list);
list.remove(t); sa.getTargets().add(t);
} list.remove(t);
}
return true;
} // pumpTgtAI() return true;
} // pumpTgtAI()
/**
* <p> /**
* getCurseCreatures. * <p>
* </p> * getCurseCreatures.
* * </p>
* @param sa *
* a {@link forge.game.spellability.SpellAbility} object. * @param sa
* @param kws * a {@link forge.game.spellability.SpellAbility} object.
* a {@link java.util.ArrayList} object. * @param kws
* @return a CardCollection. * a {@link java.util.ArrayList} object.
*/ * @return a CardCollection.
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) { */
final Player opp = ai.getOpponent(); private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa); final Player opp = ComputerUtil.getOpponentFor(ai);
if (!list.isEmpty()) { CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
list = CardLists.filter(list, new Predicate<Card>() { if (!list.isEmpty()) {
@Override list = CardLists.filter(list, new Predicate<Card>() {
public boolean apply(final Card c) { @Override
return c.hasAnyKeyword(kws); // don't add duplicate negative public boolean apply(final Card c) {
// keywords return c.hasAnyKeyword(kws); // don't add duplicate negative
} // keywords
}); }
} });
return list; }
} // getCurseCreatures() return list;
} // getCurseCreatures()
/**
* <p> /**
* debuffMandatoryTarget. * <p>
* </p> * debuffMandatoryTarget.
* * </p>
* @param sa *
* a {@link forge.game.spellability.SpellAbility} object. * @param sa
* @param mandatory * a {@link forge.game.spellability.SpellAbility} object.
* a boolean. * @param mandatory
* @return a boolean. * a boolean.
*/ * @return a boolean.
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) { */
final TargetRestrictions tgt = sa.getTargetRestrictions(); private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.getActivatingPlayer(), sa.getHostCard(), sa); CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(),
sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets(); if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false; sa.resetTargets();
} return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) { // Remove anything that's already been targeted
list.remove(c); for (final Card c : sa.getTargets().getTargetCards()) {
} list.remove(c);
}
final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponent());
final CardCollection forced = CardLists.filterControlledBy(list, ai); final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
final Card source = sa.getHostCard(); final CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) { while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
break; if (pref.isEmpty()) {
} break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").size() == 0) { Card c;
c = ComputerUtilCard.getBestCreatureAI(pref); if (CardLists.getNotType(pref, "Creature").size() == 0) {
} else { c = ComputerUtilCard.getBestCreatureAI(pref);
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true); } else {
} c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
pref.remove(c);
sa.getTargets().add(c);
} sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (forced.isEmpty()) { while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
break; if (forced.isEmpty()) {
} break;
}
// TODO - if forced targeting, just pick something without the given
// keyword // TODO - if forced targeting, just pick something without the given
Card c; // keyword
if (CardLists.getNotType(forced, "Creature").size() == 0) { Card c;
c = ComputerUtilCard.getWorstCreatureAI(forced); if (CardLists.getNotType(forced, "Creature").size() == 0) {
} else { c = ComputerUtilCard.getWorstCreatureAI(forced);
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true); } else {
} c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
forced.remove(c);
sa.getTargets().add(c);
} sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets(); if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false; sa.resetTargets();
} return false;
}
return true;
} // pumpMandatoryTarget() return true;
} // pumpMandatoryTarget()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { @Override
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>(); protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
if (sa.getTargetRestrictions() == null) {
if (mandatory) { if (sa.getTargetRestrictions() == null) {
return true; if (mandatory) {
} return true;
} else { }
return debuffTgtAI(ai, sa, kws, mandatory); } else {
} return debuffTgtAI(ai, sa, kws, mandatory);
}
return true;
} return true;
}
}
}

View File

@@ -1,54 +1,66 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiController; import forge.ai.AiController;
import forge.ai.AiPlayDecision; import forge.ai.AiPlayDecision;
import forge.ai.PlayerControllerAi; import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi; import forge.ai.SpellApiToAi;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class DelayedTriggerAi extends SpellAbilityAi { public class DelayedTriggerAi extends SpellAbilityAi {
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ("Always".equals(sa.getParam("AILogic"))) { if ("Always".equals(sa.getParam("AILogic"))) {
// TODO: improve ai // TODO: improve ai
return true; return true;
} }
final String svarName = sa.getParam("Execute"); SpellAbility trigsa = null;
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard()); if (sa.hasAdditionalAbility("Execute")) {
trigsa.setActivatingPlayer(ai); trigsa = sa.getAdditionalAbility("Execute");
} else {
if (trigsa instanceof AbilitySub) { trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa); }
} else { trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
} if (trigsa instanceof AbilitySub) {
} return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
} else {
@Override return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { }
final String svarName = sa.getParam("Execute"); }
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); @Override
trigsa.setActivatingPlayer(ai); protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
SpellAbility trigsa = null;
if (!sa.hasParam("OptionalDecider")) { if (sa.hasAdditionalAbility("Execute")) {
return aic.doTrigger(trigsa, true); trigsa = sa.getAdditionalAbility("Execute");
} else { } else {
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You")); trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
} }
} AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai);
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { if (!sa.hasParam("OptionalDecider")) {
final String svarName = sa.getParam("Execute"); return aic.doTrigger(trigsa, true);
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getSVar(svarName), sa.getHostCard()); } else {
trigsa.setActivatingPlayer(ai); return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa); }
} }
} @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
SpellAbility trigsa = null;
if (sa.hasAdditionalAbility("Execute")) {
trigsa = sa.getAdditionalAbility("Execute");
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
}
trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}

View File

@@ -1,456 +1,462 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.AiController; import forge.ai.*;
import forge.ai.AiProps; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.ai.ComputerUtil; import forge.game.card.*;
import forge.ai.ComputerUtilCard; import forge.game.cost.Cost;
import forge.ai.ComputerUtilCost; import forge.game.cost.CostPart;
import forge.ai.ComputerUtilMana; import forge.game.cost.CostSacrifice;
import forge.ai.PlayerControllerAi; import forge.game.keyword.Keyword;
import forge.ai.SpecialCardAi; import forge.game.phase.PhaseHandler;
import forge.ai.SpellAbilityAi; import forge.game.phase.PhaseType;
import forge.game.ability.AbilityUtils; import forge.game.player.Player;
import forge.game.ability.ApiType; import forge.game.spellability.SpellAbility;
import forge.game.card.Card; import forge.game.spellability.TargetRestrictions;
import forge.game.card.CardCollection; import forge.game.zone.ZoneType;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists; public class DestroyAi extends SpellAbilityAi {
import forge.game.card.CardPredicates; @Override
import forge.game.card.CounterType; public boolean chkAIDrawback(SpellAbility sa, Player ai) {
import forge.game.cost.Cost; return canPlayAI(ai, sa);
import forge.game.cost.CostPart; }
import forge.game.cost.CostSacrifice;
import forge.game.phase.PhaseHandler; @Override
import forge.game.phase.PhaseType; protected boolean canPlayAI(final Player ai, SpellAbility sa) {
import forge.game.player.Player; // AI needs to be expanded, since this function can be pretty complex
import forge.game.spellability.SpellAbility; // based on what the expected targets could be
import forge.game.spellability.TargetRestrictions; final Cost abCost = sa.getPayCosts();
import forge.game.zone.ZoneType; final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
public class DestroyAi extends SpellAbilityAi { final boolean noRegen = sa.hasParam("NoRegen");
@Override final String logic = sa.getParam("AILogic");
public boolean chkAIDrawback(SpellAbility sa, Player ai) { boolean hasXCost = false;
return canPlayAI(ai, sa);
} CardCollection list;
@Override if (abCost != null) {
protected boolean canPlayAI(final Player ai, SpellAbility sa) { if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
// AI needs to be expanded, since this function can be pretty complex return false;
// based on what the expected targets could be }
final Cost abCost = sa.getPayCosts();
final TargetRestrictions abTgt = sa.getTargetRestrictions(); if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
final Card source = sa.getHostCard(); return false;
final boolean noRegen = sa.hasParam("NoRegen"); }
final String logic = sa.getParam("AILogic");
boolean hasXCost = false; if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
CardCollection list; }
if (abCost != null) { hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false;
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) { }
return false;
} if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) { if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false; return false;
} }
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { PhaseHandler ph = ai.getGame().getPhaseHandler();
return false; if (!ph.is(PhaseType.END_OF_TURN)) {
} return false;
}
hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false; } else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
} PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!ph.is(PhaseType.END_OF_TURN) || ai.getAttackedWithCreatureThisTurn()) {
if (ComputerUtil.preventRunAwayActivations(sa)) { return false;
return false; }
} }
// Targeting if (ComputerUtil.preventRunAwayActivations(sa)) {
if (abTgt != null) { return false;
sa.resetTargets(); }
if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); // Ability that's intended to destroy own useless token to trigger Grave Pacts
sa.setTargetingPlayer(targetingPlayer); // should be fired at end of turn or when under attack after blocking to make opponent sac something
return targetingPlayer.getController().chooseTargetsFor(sa); boolean havepact = false;
}
if ("MadSarkhanDragon".equals(logic)) { // TODO replace it with look for a dies -> sacrifice trigger check
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa); havepact |= ai.isCardInPlay("Grave Pact");
} else if ("Polymorph".equals(logic)) { havepact |= ai.isCardInPlay("Butcher of Malakir");
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); havepact |= ai.isCardInPlay("Dictate of Erebos");
if (list.isEmpty()) { if ("Pactivator".equals(logic) && havepact) {
return false; if ((!ai.getGame().getPhaseHandler().isPlayerTurn(ai))
} && ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) || (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)))
for (Card c : list) { && (ai.getOpponents().getCreaturesInPlay().size() > 0)) {
if (c.hasKeyword("Indestructible")) { ai.getController().chooseTargetsFor(sa);
sa.getTargets().add(c); return true;
return true; }
} }
}
Card worst = ComputerUtilCard.getWorstAI(list); // Targeting
if (worst.isCreature() && ComputerUtilCard.evaluateCreature(worst) >= 200) { if (abTgt != null) {
return false; sa.resetTargets();
} if (sa.hasParam("TargetingPlayer")) {
if (!worst.isCreature() && worst.getCMC() > 1) { Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
return false; sa.setTargetingPlayer(targetingPlayer);
} return targetingPlayer.getController().chooseTargetsFor(sa);
sa.getTargets().add(worst); }
return true; if ("MadSarkhanDragon".equals(logic)) {
} return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa); } else if (logic != null && logic.startsWith("MinLoyalty.")) {
if ("FatalPush".equals(logic)) { int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
final int cmcMax = ai.hasRevolt() ? 4 : 2; if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax)); return false;
} }
if (sa.hasParam("AITgts")) { } else if ("Polymorph".equals(logic)) {
if (sa.getParam("AITgts").equals("BetterThanSource")) { list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
if (source.isEnchanted()) { if (list.isEmpty()) {
if (source.getEnchantedBy(false).get(0).getController().equals(ai)) { return false;
return false; }
} for (Card c : list) {
} else { if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
final int value = ComputerUtilCard.evaluateCreature(source); sa.getTargets().add(c);
list = CardLists.filter(list, new Predicate<Card>() { return true;
@Override }
public boolean apply(final Card c) { }
return ComputerUtilCard.evaluateCreature(c) > value + 30; Card worst = ComputerUtilCard.getWorstAI(list);
} if (worst.isCreature() && ComputerUtilCard.evaluateCreature(worst) >= 200) {
}); return false;
} }
} else { if (!worst.isCreature() && worst.getCMC() > 1) {
list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source); return false;
} }
} sa.getTargets().add(worst);
list = CardLists.getNotKeyword(list, "Indestructible"); return true;
if (!SpellAbilityAi.playReusable(ai, sa)) { }
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
@Override if ("FatalPush".equals(logic)) {
public boolean apply(final Card c) { final int cmcMax = ai.hasRevolt() ? 4 : 2;
//Check for cards that can be sacrificed in response list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax));
for (final SpellAbility ability : c.getAllSpellAbilities()) { }
if (ability.isAbility()) {
final Cost cost = ability.getPayCosts(); // Filter AI-specific targets if provided
for (final CostPart part : cost.getCostParts()) { list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
if (!(part instanceof CostSacrifice)) {
continue; list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
} if (CardLists.getNotType(list, "Creature").isEmpty()) {
CostSacrifice sacCost = (CostSacrifice) part; list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) { }
return false; if (!SpellAbilityAi.playReusable(ai, sa)) {
} list = CardLists.filter(list, new Predicate<Card>() {
} @Override
} public boolean apply(final Card c) {
} //Check for cards that can be sacrificed in response
if (c.hasSVar("SacMe")) { for (final SpellAbility ability : c.getAllSpellAbilities()) {
return false; if (ability.isAbility()) {
} final Cost cost = ability.getPayCosts();
//Check for undying for (final CostPart part : cost.getCostParts()) {
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0); if (!(part instanceof CostSacrifice)) {
} continue;
}); }
} CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
// If NoRegen is not set, filter out creatures that have a return false;
// regeneration shield }
if (!noRegen) { }
// TODO filter out things that might be tougher? }
list = CardLists.filter(list, new Predicate<Card>() { }
@Override if (c.hasSVar("SacMe")) {
public boolean apply(final Card c) { return false;
return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c)); }
} //Check for undying
}); return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterType.P1P1) > 0);
} }
});
if (list.isEmpty()) { }
return false;
} // If NoRegen is not set, filter out creatures that have a
// regeneration shield
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa); if (!noRegen) {
// TODO filter out things that might be tougher?
if (hasXCost) { list = CardLists.filter(list, new Predicate<Card>() {
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement. @Override
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa)); public boolean apply(final Card c) {
} return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
if (sa.hasParam("AIMaxTgtsCount")) { }
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified });
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable? }
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
} if (list.isEmpty()) {
return false;
if (maxTargets == 0) { }
// can't afford X or otherwise target anything
return false; int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
}
if (hasXCost) {
// target loop // TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
while (sa.getTargets().getNumTargeted() < maxTargets) { maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
if (list.isEmpty()) { // X can't be more than the lands we have in our hand for "discard X lands"!
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa)) if ("ScorchedEarth".equals(logic)) {
|| (sa.getTargets().getNumTargeted() == 0)) { int lands = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
sa.resetTargets(); maxTargets = Math.min(maxTargets, lands);
return false; }
} else { }
// TODO is this good enough? for up to amounts? if (sa.hasParam("AIMaxTgtsCount")) {
break; // Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
} // TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
} maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
}
Card choice = null;
// If the targets are only of one type, take the best if (maxTargets == 0) {
if (CardLists.getNotType(list, "Creature").isEmpty()) { // can't afford X or otherwise target anything
choice = ComputerUtilCard.getBestCreatureAI(list); return false;
if ("OppDestroyYours".equals(logic)) { }
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) { // target loop
return false; while (sa.getTargets().getNumTargeted() < maxTargets) {
} if (list.isEmpty()) {
} if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
if ("Pongify".equals(logic)) { || (sa.getTargets().getNumTargeted() == 0)) {
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility()); sa.resetTargets();
if (token == null) { return false;
return true; // becomes Terminate } else {
} else { // TODO is this good enough? for up to amounts?
if (source.getGame().getPhaseHandler().getPhase() break;
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant }
ComputerUtilCard.evaluateCreature(choice) < 1.5 }
* ComputerUtilCard.evaluateCreature(token)) {
return false; Card choice = null;
} // If the targets are only of one type, take the best
} if (CardLists.getNotType(list, "Creature").isEmpty()) {
} choice = ComputerUtilCard.getBestCreatureAI(list);
} else if (CardLists.getNotType(list, "Land").isEmpty()) { if ("OppDestroyYours".equals(logic)) {
choice = ComputerUtilCard.getBestLandAI(list); Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) { return false;
// Strip Mine, Wasteland - cut short if the relevant logic fails }
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) { }
return false; if ("Pongify".equals(logic)) {
} final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
} if (token == null) {
} else { return true; // becomes Terminate
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true); } else {
} if (source.getGame().getPhaseHandler().getPhase()
//option to hold removal instead only applies for single targeted removal .isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) { ComputerUtilCard.evaluateCreature(choice) < 1.5
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) { * ComputerUtilCard.evaluateCreature(token)) {
return false; return false;
} }
} }
}
if (choice == null) { // can't find anything left } else if (CardLists.getNotType(list, "Land").isEmpty()) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa)) choice = ComputerUtilCard.getBestLandAI(list);
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets(); if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
return false; // Strip Mine, Wasteland - cut short if the relevant logic fails
} else { if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
// TODO is this good enough? for up to amounts? return false;
break; }
} }
} else { } else {
// Don't destroy stolen permanents when the stealing aura can be destroyed choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
if (choice.getOwner() == ai) { }
for (Card aura : choice.getEnchantedBy(false)) { //option to hold removal instead only applies for single targeted removal
SpellAbility sp = aura.getFirstSpellAbility(); if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
if (sp != null && "GainControl".equals(sp.getParam("AILogic")) if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
&& aura.getController() != ai && sa.canTarget(aura)) { return false;
choice = aura; }
} }
}
} if (choice == null) { // can't find anything left
} if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
list.remove(choice); || (sa.getTargets().getNumTargeted() == 0)) {
sa.getTargets().add(choice); sa.resetTargets();
} return false;
} else if (sa.hasParam("Defined")) { } else {
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); // TODO is this good enough? for up to amounts?
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai) break;
|| ai.getCreaturesInPlay().size() < ai.getOpponent().getCreaturesInPlay().size() }
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai) } else {
|| ai.getLife() <= 5)) { // Don't destroy stolen permanents when the stealing aura can be destroyed
// Basic ai logic for Lethal Vapors if (choice.getOwner() == ai) {
return false; for (Card aura : choice.getEnchantedBy(false)) {
} SpellAbility sp = aura.getFirstSpellAbility();
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
if (list.isEmpty() && aura.getController() != ai && sa.canTarget(aura)) {
|| !CardLists.filterControlledBy(list, ai).isEmpty() choice = aura;
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) { }
return false; }
} }
} }
return true; list.remove(choice);
} sa.getTargets().add(choice);
}
@Override } else if (sa.hasParam("Defined")) {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
final TargetRestrictions tgt = sa.getTargetRestrictions(); if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
final Card source = sa.getHostCard(); || ai.getCreaturesInPlay().size() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
final boolean noRegen = sa.hasParam("NoRegen"); || !source.getGame().getPhaseHandler().isPlayerTurn(ai)
if (tgt != null) { || ai.getLife() <= 5)) {
sa.resetTargets(); // Basic ai logic for Lethal Vapors
return false;
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa); }
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) { || !CardLists.filterControlledBy(list, ai).isEmpty()
return false; || CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
} return false;
}
CardCollection preferred = CardLists.getNotKeyword(list, "Indestructible"); }
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents()); return true;
}
// If NoRegen is not set, filter out creatures that have a
// regeneration shield @Override
if (!noRegen) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// TODO filter out things that could regenerate in response? final TargetRestrictions tgt = sa.getTargetRestrictions();
// might be tougher? final Card source = sa.getHostCard();
preferred = CardLists.filter(preferred, new Predicate<Card>() { final boolean noRegen = sa.hasParam("NoRegen");
@Override if (tgt != null) {
public boolean apply(final Card c) { sa.resetTargets();
return c.getShieldCount() == 0;
} CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
}); list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
}
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (sa.hasParam("AITgts")) { return false;
if (sa.getParam("AITgts").equals("BetterThanSource")) { }
if (source.isEnchanted()) {
if (source.getEnchantedBy(false).get(0).getController().equals(ai)) { CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
preferred.clear(); preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
} if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
} else { preferred = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, preferred, false);
final int value = ComputerUtilCard.evaluateCreature(source); }
preferred = CardLists.filter(preferred, new Predicate<Card>() {
@Override // If NoRegen is not set, filter out creatures that have a
public boolean apply(final Card c) { // regeneration shield
return ComputerUtilCard.evaluateCreature(c) > value + 30; if (!noRegen) {
} // TODO filter out things that could regenerate in response?
}); // might be tougher?
} preferred = CardLists.filter(preferred, new Predicate<Card>() {
} else { @Override
preferred = CardLists.getValidCards(preferred, sa.getParam("AITgts"), sa.getActivatingPlayer(), source); public boolean apply(final Card c) {
} return c.getShieldCount() == 0;
} }
});
for (final Card c : preferred) { }
list.remove(c);
} // Filter AI-specific targets if provided
preferred = ComputerUtil.filterAITgts(sa, ai, (CardCollection)preferred, true);
if (preferred.isEmpty() && !mandatory) {
return false; for (final Card c : preferred) {
} list.remove(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
if (preferred.isEmpty()) { if (preferred.isEmpty() && !mandatory) {
if (sa.getTargets().getNumTargeted() == 0 return false;
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) { }
if (!mandatory) {
sa.resetTargets(); while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
return false; if (preferred.isEmpty()) {
} else { if (sa.getTargets().getNumTargeted() == 0
break; || sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
} if (!mandatory) {
} else { sa.resetTargets();
break; return false;
} } else {
} else { break;
Card c; }
if (CardLists.getNotType(preferred, "Creature").isEmpty()) { } else {
c = ComputerUtilCard.getBestCreatureAI(preferred); break;
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) { }
c = ComputerUtilCard.getBestLandAI(preferred); } else {
} else { Card c;
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false); if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
} c = ComputerUtilCard.getBestCreatureAI(preferred);
sa.getTargets().add(c); } else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
preferred.remove(c); c = ComputerUtilCard.getBestLandAI(preferred);
} } else {
} c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) { sa.getTargets().add(c);
if (list.isEmpty()) { preferred.remove(c);
break; }
} else { }
Card c;
if (CardLists.getNotType(list, "Creature").isEmpty()) { while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy if (list.isEmpty()) {
&& sa.getUniqueTargets().get(0) instanceof Card) { break;
// basic ai for Diaochan } else {
c = (Card) sa.getUniqueTargets().get(0); Card c;
} else { if (CardLists.getNotType(list, "Creature").isEmpty()) {
c = ComputerUtilCard.getWorstCreatureAI(list); if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
} && sa.getUniqueTargets().get(0) instanceof Card) {
} else { // basic ai for Diaochan
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false); c = (Card) sa.getUniqueTargets().get(0);
} } else {
sa.getTargets().add(c); c = ComputerUtilCard.getWorstCreatureAI(list);
list.remove(c); }
} } else {
} c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) { sa.getTargets().add(c);
return false; list.remove(c);
} }
} else { }
if (!mandatory) {
return false; if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
} return false;
} }
} else {
return true; if (!mandatory) {
} return false;
}
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) { }
if (tgtLand == null) { return false; }
return true;
Player tgtPlayer = tgtLand.getController(); }
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
// AI profile-dependent properties if (tgtLand == null) { return false; }
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK); Player tgtPlayer = tgtLand.getController();
int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK); int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING); // AI profile-dependent properties
boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP); AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
PhaseHandler ph = ai.getGame().getPhaseHandler(); int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai)) int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
|| (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2)); boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop;
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
// Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent PhaseHandler ph = ai.getGame().getPhaseHandler();
// (triggers either if the opponent skipped a land drop or if there are quite a few lands already in play but only one of the given type) boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai))
CardCollection oppLands = tgtPlayer.getLandsInPlay(); || (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2));
boolean canColorLock = (oppSkippedLandDrop || oppLands.size() > 3) boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop;
&& tgtLand.isBasicLand() && CardLists.filter(oppLands, CardPredicates.nameEquals(tgtLand.getName())).size() == 1;
// Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target, // (triggers either if the opponent skipped a land drop or if there are quite a few lands already in play but only one of the given type)
// consider killing it off unless there's too much potential tempo loss. CardCollection oppLands = tgtPlayer.getLandsInPlay();
// TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative boolean canColorLock = (oppSkippedLandDrop || oppLands.size() > 3)
// (dual/triple mana that opens access to a certain color) lands && tgtLand.isBasicLand() && CardLists.filter(oppLands, CardPredicates.nameEquals(tgtLand.getName())).size() == 1;
boolean nonBasicTgt = !tgtLand.isBasicLand();
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target,
// Try not to lose tempo too much and not to mana-screw yourself when considering this logic // consider killing it off unless there's too much potential tempo loss.
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size(); // TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size(); // (dual/triple mana that opens access to a certain color) lands
boolean nonBasicTgt = !tgtLand.isBasicLand();
// If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop; // Try not to lose tempo too much and not to mana-screw yourself when considering this logic
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
boolean timingCheck = canManaLock || canColorLock || nonBasicTgt; int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
|| ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck)); // If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
// For Ghost Quarter, only use it if you have either more lands in play than your opponent
// or the same number of lands but an extra land in hand (otherwise the AI plays too suboptimally) boolean timingCheck = canManaLock || canColorLock || nonBasicTgt;
if ("GhostQuarter".equals(logic)) { boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
return tempoCheck && (numLandsOTB > oppLands.size() || (numLandsOTB == oppLands.size() && numLandsInHand > 0)); || ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck));
} else {
return tempoCheck; // For Ghost Quarter, only use it if you have either more lands in play than your opponent
} // or the same number of lands but an extra land in hand (otherwise the AI plays too suboptimally)
} if ("GhostQuarter".equals(logic)) {
} return tempoCheck && (numLandsOTB > oppLands.size() || (numLandsOTB == oppLands.size() && numLandsInHand > 0));
} else {
return tempoCheck;
}
}
}

View File

@@ -1,158 +1,177 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.*; import forge.ai.*;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.phase.PhaseType; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.phase.PhaseType;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.spellability.SpellAbility;
import forge.game.combat.Combat; import forge.game.zone.ZoneType;
import forge.game.combat.Combat;
public class DestroyAllAi extends SpellAbilityAi {
public class DestroyAllAi extends SpellAbilityAi {
private static final Predicate<Card> predicate = new Predicate<Card>() {
@Override private static final Predicate<Card> predicate = new Predicate<Card>() {
public boolean apply(final Card c) { @Override
return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0); public boolean apply(final Card c) {
} return !(c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getSVar("SacMe").length() > 0);
}; }
};
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean) /* (non-Javadoc)
*/ * @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
@Override */
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { @Override
if (mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return true; if (mandatory) {
} return true;
}
return doMassRemovalLogic(ai, sa);
} return doMassRemovalLogic(ai, sa);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { @Override
//TODO: Check for bad outcome public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return true; //TODO: Check for bad outcome
} return true;
}
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) { @Override
// AI needs to be expanded, since this function can be pretty complex protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// based on what the expected targets could be // AI needs to be expanded, since this function can be pretty complex
final Cost abCost = sa.getPayCosts(); // based on what the expected targets could be
final Card source = sa.getHostCard(); final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
if (abCost != null) {
// AI currently disabled for some costs if (abCost != null) {
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) { // AI currently disabled for some costs
return false; if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
} return false;
} }
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) { // prevent run-away activations - first time will always return true
return false; if (ComputerUtil.preventRunAwayActivations(sa)) {
} return false;
}
return doMassRemovalLogic(ai, sa);
} return doMassRemovalLogic(ai, sa);
}
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard(); public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
Player opponent = ai.getOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()? final Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
final int CREATURE_EVAL_THRESHOLD = 200; Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
String valid = ""; final int CREATURE_EVAL_THRESHOLD = 200;
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards"); if (logic.equals("Always")) {
} return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
}
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value. String valid = "";
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); if (sa.hasParam("ValidCards")) {
source.setSVar("PayX", Integer.toString(xPay)); valid = sa.getParam("ValidCards");
valid = valid.replace("X", Integer.toString(xPay)); }
}
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), // Set PayX here to maximum value.
valid.split(","), source.getController(), source, sa); final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.setSVar("PayX", Integer.toString(xPay));
source.getController(), source, sa); valid = valid.replace("X", Integer.toString(xPay));
}
opplist = CardLists.filter(opplist, predicate);
ailist = CardLists.filter(ailist, predicate); CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
if (opplist.isEmpty()) { valid.split(","), source.getController(), source, sa);
return false; CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
} source.getController(), source, sa);
if (sa.usesTargeting()) { opplist = CardLists.filter(opplist, predicate);
sa.resetTargets(); ailist = CardLists.filter(ailist, predicate);
if (sa.canTarget(opponent)) { if (opplist.isEmpty()) {
sa.getTargets().add(opponent); return false;
ailist.clear(); }
} else {
return false; if (sa.usesTargeting()) {
} sa.resetTargets();
} if (sa.canTarget(opponent)) {
sa.getTargets().add(opponent);
// if only creatures are affected evaluate both lists and pass only if ailist.clear();
// human creatures are more valuable } else {
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) { return false;
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) { }
return true; }
}
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) { if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
return false; && (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
} return true;
}
// test whether the human can kill the ai next turn
Combat combat = new Combat(opponent); // If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
boolean containsAttacker = false; if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
for (Card att : opponent.getCreaturesInPlay()) { && (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) { && ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
combat.addAttacker(att, ai); return true;
containsAttacker = containsAttacker | opplist.contains(att); }
}
} // if only creatures are affected evaluate both lists and pass only if
if (!containsAttacker) { // human creatures are more valuable
return false; if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
} if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
AiBlockController block = new AiBlockController(ai); return true;
block.assignBlockersForCombat(combat); }
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) { if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return true; return false;
} }
return false;
} // only lands involved // test whether the human can kill the ai next turn
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) { Combat combat = new Combat(opponent);
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) { boolean containsAttacker = false;
return true; for (Card att : opponent.getCreaturesInPlay()) {
} if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes combat.addAttacker(att, ai);
CardCollection aiCreatures = ai.getCreaturesInPlay(); containsAttacker = containsAttacker | opplist.contains(att);
CardCollection oppCreatures = opponent.getCreaturesInPlay(); }
if (!oppCreatures.isEmpty()) { }
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) { if (!containsAttacker) {
return false; return false;
} }
} AiBlockController block = new AiBlockController(ai);
// check if the AI would lose more lands than the opponent would block.assignBlockersForCombat(combat);
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
return false; if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
} return true;
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable }
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) { return false;
return false; } // only lands involved
} else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
return true; return true;
} }
} // evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = opponent.getCreaturesInPlay();
if (!oppCreatures.isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
return false;
}
}
// check if the AI would lose more lands than the opponent would
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
return false;
}
return true;
}
}

View File

@@ -1,159 +1,155 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtil; import forge.ai.*;
import forge.ai.ComputerUtilAbility; import forge.game.Game;
import forge.ai.ComputerUtilCard; import forge.game.ability.AbilityUtils;
import forge.ai.ComputerUtilMana; import forge.game.card.Card;
import forge.ai.SpecialCardAi; import forge.game.phase.PhaseType;
import forge.ai.SpellAbilityAi; import forge.game.player.Player;
import forge.game.Game; import forge.game.player.PlayerActionConfirmMode;
import forge.game.ability.AbilityUtils; import forge.game.spellability.AbilitySub;
import forge.game.card.Card; import forge.game.spellability.SpellAbility;
import forge.game.card.CounterType; import forge.game.zone.ZoneType;
import forge.game.phase.PhaseType; import forge.util.TextUtil;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub; public class DigAi extends SpellAbilityAi {
import forge.game.spellability.SpellAbility; /* (non-Javadoc)
import forge.game.zone.ZoneType; * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
import forge.util.TextUtil; */
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
public class DigAi extends SpellAbilityAi { final Game game = ai.getGame();
/* (non-Javadoc) Player opp = ComputerUtil.getOpponentFor(ai);
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) final Card host = sa.getHostCard();
*/ Player libraryOwner = ai;
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { if (sa.usesTargeting()) {
final Game game = ai.getGame(); sa.resetTargets();
Player opp = ai.getOpponent(); if (!opp.canBeTargetedBy(sa)) {
final Card host = sa.getHostCard(); return false;
Player libraryOwner = ai; } else {
sa.getTargets().add(opp);
if (sa.usesTargeting()) { }
sa.resetTargets(); libraryOwner = opp;
if (!opp.canBeTargetedBy(sa)) { }
return false;
} else { // return false if nothing to dig into
sa.getTargets().add(opp); if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
} return false;
libraryOwner = opp; }
}
if ("Never".equals(sa.getParam("AILogic"))) {
// return false if nothing to dig into return false;
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) { } else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
return false; return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
} }
if ("Never".equals(sa.getParam("AILogic"))) { // don't deck yourself
return false; if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
} int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
// don't deck yourself return false;
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) { }
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa); }
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
return false; // Don't use draw abilities before main 2 if possible
} if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
} && !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
// Don't use draw abilities before main 2 if possible }
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) { final String num = sa.getParam("DigNum");
return false; if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
} // By default, set PayX here to maximum value.
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
final String num = sa.getParam("DigNum"); int manaToSave = 0;
if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
// By default, set PayX here to maximum value. // Special logic that asks the AI to conserve a certain amount of mana when paying X
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) { if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
int manaToSave = 0; manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
}
// Special logic that asks the AI to conserve a certain amount of mana when paying X
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) { int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]); if (numCards <= 0) {
} return false;
}
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave; host.setSVar("PayX", Integer.toString(numCards));
if (numCards <= 0) { }
return false; }
}
host.setSVar("PayX", Integer.toString(numCards)); if (SpellAbilityAi.playReusable(ai, sa)) {
} return true;
} }
if (SpellAbilityAi.playReusable(ai, sa)) { if ((!game.getPhaseHandler().getNextTurn().equals(ai)
return true; || game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
} && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
if ((!game.getPhaseHandler().getNextTurn().equals(ai) && !ComputerUtil.activateForCost(sa, ai)) {
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN)) return false;
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa) }
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
&& !ComputerUtil.activateForCost(sa, ai)) { if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
return false; return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
} }
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) { return !ComputerUtil.preventRunAwayActivations(sa);
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa); }
}
@Override
return !ComputerUtil.preventRunAwayActivations(sa); protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
} final Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.usesTargeting()) {
@Override sa.resetTargets();
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { if (mandatory && sa.canTarget(opp)) {
final Player opp = ai.getOpponent(); sa.getTargets().add(opp);
if (sa.usesTargeting()) { } else if (mandatory && sa.canTarget(ai)) {
sa.resetTargets(); sa.getTargets().add(ai);
if (mandatory && sa.canTarget(opp)) { }
sa.getTargets().add(opp); }
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai); // Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
} if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
} int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar). if (numCards <= 0) {
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) { return mandatory;
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]); }
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave; sa.getHostCard().setSVar("PayX", Integer.toString(numCards));
if (numCards <= 0) { }
return mandatory;
} return true;
sa.getHostCard().setSVar("PayX", Integer.toString(numCards)); }
}
@Override
return true; public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
} if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
return ComputerUtilCard.getWorstPermanentAI(valid, false, true, false, false);
@Override } else {
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) { return ComputerUtilCard.getBestAI(valid);
Card chosen = ComputerUtilCard.getBestAI(valid); }
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) { }
return ComputerUtilCard.getWorstAI(valid);
} /* (non-Javadoc)
return chosen; * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
} */
@Override
/* (non-Javadoc) public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) Card topc = player.getZone(ZoneType.Library).get(0);
*/
@Override // AI actions for individual cards (until this AI can be generalized)
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { if (sa.getHostCard() != null) {
Card topc = player.getZone(ZoneType.Library).get(0); if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
// for Explorer's Scope, always put a land on the battlefield tapped
// AI actions for individual cards (until this AI can be generalized) // (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects)
if (sa.getHostCard() != null) { return true;
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) { } else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
// for Explorer's Scope, always put a land on the battlefield tapped return true;
// (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects) }
return true; }
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
return true; // looks like perfect code for Delver of Secrets, but what about other cards?
} return topc.isInstant() || topc.isSorcery();
} }
}
// looks like perfect code for Delver of Secrets, but what about other cards?
return topc.isInstant() || topc.isSorcery();
}
}

View File

@@ -1,117 +1,145 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtilMana;
import forge.game.card.Card; import forge.ai.SpellAbilityAi;
import forge.game.card.CardLists; import forge.game.card.Card;
import forge.game.card.CardPredicates; import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.card.CardPredicates;
import forge.game.player.PlayerActionConfirmMode; import forge.game.phase.PhaseType;
import forge.game.spellability.AbilitySub; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.player.PlayerActionConfirmMode;
import forge.game.zone.ZoneType; import forge.game.spellability.AbilitySub;
import forge.util.MyRandom; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List; import forge.util.MyRandom;
import java.util.Random;
import java.util.List;
public class DigUntilAi extends SpellAbilityAi {
public class DigUntilAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { @Override
Card source = sa.getHostCard(); protected boolean canPlayAI(Player ai, SpellAbility sa) {
double chance = .4; // 40 percent chance with instant speed stuff Card source = sa.getHostCard();
if (SpellAbilityAi.isSorcerySpeed(sa)) { final String logic = sa.getParamOrDefault("AILogic", "");
chance = .667; // 66.7% chance for sorcery speed (since it will double chance = .4; // 40 percent chance with instant speed stuff
// never activate EOT) if (SpellAbilityAi.isSorcerySpeed(sa)) {
} chance = .667; // 66.7% chance for sorcery speed (since it will
final Random r = MyRandom.getRandom(); // never activate EOT)
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1); }
// if we don't use anything now, we wasted our opportunity.
Player libraryOwner = ai; if ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN))
Player opp = ai.getOpponent(); && (!ai.getGame().getPhaseHandler().isPlayerTurn(ai))) {
chance = 1;
if (sa.usesTargeting()) { }
sa.resetTargets();
if (!sa.canTarget(opp)) { final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
return false;
} else { Player libraryOwner = ai;
sa.getTargets().add(opp); Player opp = ComputerUtil.getOpponentFor(ai);
}
libraryOwner = opp; if ("DontMillSelf".equals(logic)) {
} else { // A card that digs for specific things and puts everything revealed before it into graveyard
if (sa.hasParam("Valid")) { // (e.g. Hermit Druid) - don't use it to mill itself and also make sure there's enough playable
final String valid = sa.getParam("Valid"); // material in the library after using it several times.
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) { // TODO: maybe this should happen for any DigUntil SA with RevealedDestination$ Graveyard?
return false; if (ai.getCardsIn(ZoneType.Library).size() < 20) {
} return false;
} }
} if ("Land.Basic".equals(sa.getParam("Valid"))
&& !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).isEmpty()) {
final String num = sa.getParam("Amount"); // We already have a mana-producing land in hand, so bail
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { // until opponent's end of turn phase!
// Set PayX here to maximum value. // But we still want more (and want to fill grave) if nothing better to do then
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) { // This is important for Replenish/Living Death type decks
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai); if (!((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN))
if (numCards <= 0) { && (!ai.getGame().getPhaseHandler().isPlayerTurn(ai)))) {
return false; return false;
} }
source.setSVar("PayX", Integer.toString(numCards)); }
} }
}
if (sa.usesTargeting()) {
// return false if nothing to dig into sa.resetTargets();
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) { if (!sa.canTarget(opp)) {
return false; return false;
} } else {
sa.getTargets().add(opp);
return randomReturn; }
} libraryOwner = opp;
} else {
@Override if (sa.hasParam("Valid")) {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { final String valid = sa.getParam("Valid");
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) {
if (sa.usesTargeting()) { return false;
sa.resetTargets(); }
if (sa.isCurse()) { }
for (Player opp : ai.getOpponents()) { }
if (sa.canTarget(opp)) {
sa.getTargets().add(opp); final String num = sa.getParam("Amount");
break; if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
} // Set PayX here to maximum value.
} if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) { int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
sa.getTargets().add(ai); if (numCards <= 0) {
} return false;
} else { }
if (sa.canTarget(ai)) { source.setSVar("PayX", Integer.toString(numCards));
sa.getTargets().add(ai); }
} }
}
} // return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return true; return false;
} }
/* (non-Javadoc) return randomReturn;
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) }
*/
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic"); if (sa.usesTargeting()) {
if ("OathOfDruids".equals(logic)) { sa.resetTargets();
final List<Card> creaturesInLibrary = if (sa.isCurse()) {
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES); for (Player opp : ai.getOpponents()) {
final List<Card> creaturesInBattlefield = if (sa.canTarget(opp)) {
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); sa.getTargets().add(opp);
// if there are at least 3 creatures in library, break;
// or none in play with one in library, oath }
return creaturesInLibrary.size() > 2 }
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0); if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
sa.getTargets().add(ai);
} }
} } else {
return true; if (sa.canTarget(ai)) {
} sa.getTargets().add(ai);
} }
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if ("OathOfDruids".equals(logic)) {
final List<Card> creaturesInLibrary =
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
final List<Card> creaturesInBattlefield =
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
// if there are at least 3 creatures in library,
// or none in play with one in library, oath
return creaturesInLibrary.size() > 2
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
}
}
return true;
}
}

View File

@@ -1,192 +1,222 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtil; import forge.ai.*;
import forge.ai.ComputerUtilAbility; import forge.game.ability.AbilityUtils;
import forge.ai.ComputerUtilCost; import forge.game.card.Card;
import forge.ai.ComputerUtilMana; import forge.game.card.CardCollectionView;
import forge.ai.SpellAbilityAi; import forge.game.card.CardLists;
import forge.game.ability.AbilityUtils; import forge.game.card.CardPredicates;
import forge.game.card.Card; import forge.game.cost.Cost;
import forge.game.cost.Cost; import forge.game.phase.PhaseType;
import forge.game.phase.PhaseType; import forge.game.player.Player;
import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerActionConfirmMode; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions;
import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType;
import forge.game.zone.ZoneType; import forge.util.MyRandom;
import forge.util.MyRandom;
import java.util.List;
import java.util.List;
import java.util.Random; public class DiscardAi extends SpellAbilityAi {
public class DiscardAi extends SpellAbilityAi { @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
@Override final TargetRestrictions tgt = sa.getTargetRestrictions();
protected boolean canPlayAI(Player ai, SpellAbility sa) { final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Card source = sa.getHostCard(); final Cost abCost = sa.getPayCosts();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final String aiLogic = sa.getParamOrDefault("AILogic", "");
final Cost abCost = sa.getPayCosts();
if (abCost != null) {
if (abCost != null) { // AI currently disabled for these costs
// AI currently disabled for these costs if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) { return false;
return false; }
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) { return false;
return false; }
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { return false;
return false; }
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { return false;
return false; }
}
}
}
if ("Chandra, Flamecaller".equals(sourceName)) {
if ("Chandra, Flamecaller".equals(sourceName)) { final int hand = ai.getCardsIn(ZoneType.Hand).size();
final int hand = ai.getCardsIn(ZoneType.Hand).size(); return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand)); }
}
if (aiLogic.equals("VolrathsShapeshifter")) {
final boolean humanHasHand = ai.getOpponent().getCardsIn(ZoneType.Hand).size() > 0; return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
}
if (tgt != null) {
if (!discardTargetAI(ai, sa)) { final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
return false;
} if (tgt != null) {
} else { if (!discardTargetAI(ai, sa)) {
// TODO: Add appropriate restrictions return false;
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa); }
} else {
if (players.size() == 1) { // TODO: Add appropriate restrictions
if (players.get(0) == ai) { final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
// the ai should only be using something like this if he has
// few cards in hand, if (players.size() == 1) {
// cards like this better have a good drawback to be in the if (players.get(0) == ai) {
// AIs deck // the ai should only be using something like this if he has
} else { // few cards in hand,
// defined to the human, so that's fine as long the human // cards like this better have a good drawback to be in the
// has cards // AIs deck
if (!humanHasHand) { } else {
return false; // defined to the human, so that's fine as long the human
} // has cards
} if (!humanHasHand) {
} else { return false;
// Both players discard, any restrictions? }
} }
} } else {
// Both players discard, any restrictions?
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() if (sa.hasParam("NumCards")) {
.getCardsIn(ZoneType.Hand).size()); if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
if (cardsToDiscard < 1) { // Set PayX here to maximum value.
return false; final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
} .getCardsIn(ZoneType.Hand).size());
source.setSVar("PayX", Integer.toString(cardsToDiscard)); if (cardsToDiscard < 1) {
} else { return false;
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) { }
return false; source.setSVar("PayX", Integer.toString(cardsToDiscard));
} } else {
} if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
} return false;
}
// TODO: Implement support for Discard AI for cards with AnyNumber set to true. }
}
// Don't use draw abilities before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) // TODO: Improve support for Discard AI for cards with AnyNumber set to true.
&& !sa.hasParam("ActivationPhases")) { if (sa.hasParam("AnyNumber")) {
return false; if ("DiscardUncastableAndExcess".equals(aiLogic)) {
} final CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
final int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
// Don't tap creatures that may be able to block int numDiscard = 0;
if (ComputerUtil.waitForBlocking(sa)) { int numOppInHand = 0;
return false; for (Player p : ai.getGame().getPlayers()) {
} if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
numOppInHand = p.getCardsIn(ZoneType.Hand).size();
final Random r = MyRandom.getRandom(); }
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn()); }
for (Card c : inHand) {
// some other variables here, like handsize vs. maxHandSize if (c.equals(sa.getHostCard())) { continue; }
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
return randomReturn; if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), ai)) {
} // discardCanPlayAI() numDiscard++;
}
private boolean discardTargetAI(final Player ai, final SpellAbility sa) { if ((c.isLand() && numLandsOTB >= 5) || (c.getFirstSpellAbility() != null && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getFirstSpellAbility(), ai))) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (numDiscard + 1 <= numOppInHand) {
Player opp = ai.getOpponent(); numDiscard++;
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) { }
return false; }
} }
if (tgt != null) { if (numDiscard == 0) {
if (sa.canTarget(opp)) { return false;
sa.getTargets().add(opp); }
return true; }
} }
}
return false; // Don't use draw abilities before main 2 if possible
} // discardTargetAI() if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { // Don't tap creatures that may be able to block
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (ComputerUtil.waitForBlocking(sa)) {
if (tgt != null) { return false;
Player opp = ai.getOpponent(); }
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) { boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) { // some other variables here, like handsize vs. maxHandSize
sa.getTargets().add(ai);
} else { return randomReturn;
return false; } // discardCanPlayAI()
}
} private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
} else { final TargetRestrictions tgt = sa.getTargetRestrictions();
if (sa.hasParam("AILogic")) { Player opp = ComputerUtil.getOpponentFor(ai);
if ("AtLeast2".equals(sa.getParam("AILogic"))) { if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa); return false;
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) { }
return false; if (tgt != null) {
} if (sa.canTarget(opp)) {
} sa.getTargets().add(opp);
} return true;
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() return false;
.getCardsIn(ZoneType.Hand).size()); } // discardTargetAI()
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
}
}
@Override
return true; protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
} // discardTrigger() final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
@Override Player opp = ComputerUtil.getOpponentFor(ai);
public boolean chkAIDrawback(SpellAbility sa, Player ai) { if (!discardTargetAI(ai, sa)) {
// Drawback AI improvements if (mandatory && sa.canTarget(opp)) {
// if parent draws cards, make sure cards in hand + cards drawn > 0 sa.getTargets().add(opp);
final TargetRestrictions tgt = sa.getTargetRestrictions(); } else if (mandatory && sa.canTarget(ai)) {
if (tgt != null) { sa.getTargets().add(ai);
return discardTargetAI(ai, sa); } else {
} return false;
// TODO: check for some extra things }
return true; }
} // discardCheckDrawbackAI() } else {
if (sa.hasParam("AILogic")) {
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
if ( mode == PlayerActionConfirmMode.Random ) { // if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards return false;
return true; }
} }
return super.confirmAction(player, sa, mode, message); }
} 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), ComputerUtil.getOpponentFor(ai)
.getCardsIn(ZoneType.Hand).size());
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
}
}
return true;
} // discardTrigger()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// Drawback AI improvements
// if parent draws cards, make sure cards in hand + cards drawn > 0
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
return discardTargetAI(ai, sa);
}
// TODO: check for some extra things
return true;
} // discardCheckDrawbackAI()
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if ( mode == PlayerActionConfirmMode.Random ) { //
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
return true;
}
return super.confirmAction(player, sa, mode, message);
}
}

View File

@@ -1,91 +1,90 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtil;
import forge.game.ability.AbilityUtils; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.ability.AbilityUtils;
import forge.game.player.Player; import forge.game.card.Card;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.util.MyRandom; import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random; import java.util.List;
public class DrainManaAi extends SpellAbilityAi { public class DrainManaAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI cannot use this properly until he can use SAs during Humans turn // AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Player opp = ai.getOpponent(); final Player opp = ComputerUtil.getOpponentFor(ai);
final Random r = MyRandom.getRandom(); boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (tgt == null) {
if (tgt == null) { // assume we are looking to tap human's stuff
// assume we are looking to tap human's stuff // TODO - check for things with untap abilities, and don't tap
// TODO - check for things with untap abilities, and don't tap // those.
// those. final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
if (!defined.contains(opp)) { return false;
return false; }
} } else {
} else { sa.resetTargets();
sa.resetTargets(); sa.getTargets().add(opp);
sa.getTargets().add(opp); }
}
return randomReturn;
return randomReturn; }
}
@Override
@Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { final Player opp = ComputerUtil.getOpponentFor(ai);
final Player opp = ai.getOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard();
final Card source = sa.getHostCard();
if (null == tgt) {
if (null == tgt) { if (mandatory) {
if (mandatory) { return true;
return true; } else {
} else { final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
if (!defined.contains(opp)) { return false;
return false; }
} }
}
return true;
return true; } else {
} else { sa.resetTargets();
sa.resetTargets(); sa.getTargets().add(opp);
sa.getTargets().add(opp); }
}
return true;
return true; }
}
@Override
@Override public boolean chkAIDrawback(SpellAbility sa, Player ai) {
public boolean chkAIDrawback(SpellAbility sa, Player ai) { // AI cannot use this properly until he can use SAs during Humans turn
// AI cannot use this properly until he can use SAs during Humans turn final TargetRestrictions tgt = sa.getTargetRestrictions();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard();
final Card source = sa.getHostCard();
boolean randomReturn = true;
boolean randomReturn = true;
if (tgt == null) {
if (tgt == null) { final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (defined.contains(ai)) {
if (defined.contains(ai)) { return false;
return false; }
} } else {
} else { sa.resetTargets();
sa.resetTargets(); sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
sa.getTargets().add(ai.getOpponent()); }
}
return randomReturn;
return randomReturn; }
} }
}

View File

@@ -26,10 +26,7 @@ import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.cost.Cost; import forge.game.cost.*;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
import forge.game.cost.PaymentDecision;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -86,7 +83,7 @@ public class DrawAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) { protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source)) { if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
return false; return false;
} }
@@ -126,6 +123,16 @@ public class DrawAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) { protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
String logic = sa.getParamOrDefault("AILogic", "");
if (logic.startsWith("LifeLessThan.")) {
// LifeLessThan logic presupposes activation as soon as possible in an
// attempt to save the AI from dying
return true;
} else if (logic.equals("AlwaysAtOppEOT")) {
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
}
// Don't use draw abilities before main 2 if possible // Don't use draw abilities before main 2 if possible
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) { && !ComputerUtil.castSpellInMain1(ai, sa)) {
@@ -170,7 +177,7 @@ public class DrawAi extends SpellAbilityAi {
int numHand = ai.getCardsIn(ZoneType.Hand).size(); int numHand = ai.getCardsIn(ZoneType.Hand).size();
if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) { if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) {
return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS, return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS,
CardPredicates.isType("Jace")).size() <= 0; CardPredicates.isType("Jace")).size() <= 0;
} }
if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) { if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) {
@@ -204,6 +211,7 @@ public class DrawAi extends SpellAbilityAi {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final boolean drawback = sa.getParent() != null; final boolean drawback = sa.getParent() != null;
final Game game = ai.getGame(); final Game game = ai.getGame();
final String logic = sa.getParamOrDefault("AILogic", "");
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size(); int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size(); final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
@@ -240,13 +248,40 @@ public class DrawAi extends SpellAbilityAi {
} }
} }
// Logic for cards that require special handling if (num != null && num.equals("ChosenX")) {
if (sa.hasParam("AILogic")) { if (sa.getSVar("X").equals("XChoice")) {
if ("YawgmothsBargain".equals(sa.getParam("AILogic"))) { // Draw up to max hand size but leave at least 3 in library
return SpecialCardAi.YawgmothsBargain.consider(ai, sa); numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
if (sa.getPayCosts() != null) {
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
// [Necrologia, Pay X Life : Draw X Cards]
// Don't draw more than what's "safe" and don't risk a near death experience
// Maybe would be better to check for "serious danger" and take more risk?
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
numCards--;
}
} else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
// [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
// TODO: Add special logic to limit/otherwise modify the ChosenX value here
// Skip this ability if nothing is to be chosen for sacrifice
if (numCards <= 0) {
return false;
}
}
}
sa.setSVar("ChosenX", Integer.toString(numCards));
source.setSVar("ChosenX", Integer.toString(numCards));
} }
} }
// Logic for cards that require special handling
if ("YawgmothsBargain".equals(logic)) {
return SpecialCardAi.YawgmothsBargain.consider(ai, sa);
}
// Generic logic for all cards that do not need any special handling // Generic logic for all cards that do not need any special handling
// TODO: if xPaid and one of the below reasons would fail, instead of // TODO: if xPaid and one of the below reasons would fail, instead of
@@ -312,6 +347,13 @@ public class DrawAi extends SpellAbilityAi {
return true; return true;
} }
} }
// we're trying to save ourselves from death
// (e.g. Bargain), so target the opp anyway
if (logic.startsWith("LifeLessThan.")) {
int threshold = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
sa.getTargets().add(oppA);
return ai.getLife() < threshold;
}
} }
boolean aiTarget = sa.canTarget(ai); boolean aiTarget = sa.canTarget(ai);

View File

@@ -1,284 +1,339 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import com.google.common.base.Predicate; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
import forge.ai.SpecialCardAi; import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi; import forge.ai.SpellApiToAi;
import forge.game.Game; import forge.game.Game;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.*;
import forge.game.card.CardLists; import forge.game.combat.CombatUtil;
import forge.game.combat.CombatUtil; import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
public class EffectAi extends SpellAbilityAi { public class EffectAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(final Player ai,final SpellAbility sa) { protected boolean canPlayAI(final Player ai,final SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
final Random r = MyRandom.getRandom(); boolean randomReturn = MyRandom.getRandom().nextFloat() <= .6667;
boolean randomReturn = r.nextFloat() <= .6667; String logic = "";
String logic = "";
if (sa.hasParam("AILogic")) {
if (sa.hasParam("AILogic")) { logic = sa.getParam("AILogic");
logic = sa.getParam("AILogic"); final PhaseHandler phase = game.getPhaseHandler();
final PhaseHandler phase = game.getPhaseHandler(); if (logic.equals("BeginningOfOppTurn")) {
if (logic.equals("BeginningOfOppTurn")) { if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) { return false;
return false; }
} randomReturn = true;
randomReturn = true; } else if (logic.equals("EndOfOppTurn")) {
} else if (logic.equals("EndOfOppTurn")) { if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) { return false;
return false; }
} randomReturn = true;
randomReturn = true; } else if (logic.equals("KeepOppCreatsLandsTapped")) {
} else if (logic.equals("Fog")) { for (Player opp : ai.getOpponents()) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) { boolean worthHolding = false;
return false; CardCollectionView oppCreatsLands = CardLists.filter(opp.getCardsIn(ZoneType.Battlefield),
} Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES));
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { CardCollectionView oppCreatsLandsTapped = CardLists.filter(oppCreatsLands, CardPredicates.Presets.TAPPED);
return false;
} if (oppCreatsLandsTapped.size() >= 3 || oppCreatsLands.size() == oppCreatsLandsTapped.size()) {
if (!game.getStack().isEmpty()) { worthHolding = true;
return false; }
} if (!worthHolding) {
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { return false;
return false; }
} randomReturn = true;
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) { }
return false; } else if (logic.equals("Fog")) {
} if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); return false;
if (tgt != null) { }
sa.resetTargets(); if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
if (tgt.canOnlyTgtOpponent()) { return false;
boolean canTgt = false; }
if (!game.getStack().isEmpty()) {
for (Player opp2 : ai.getOpponents()) { return false;
if (sa.canTarget(opp2)) { }
sa.getTargets().add(opp2); if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
canTgt = true; return false;
break; }
} if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
} return false;
}
if (!canTgt) { final TargetRestrictions tgt = sa.getTargetRestrictions();
return false; if (tgt != null) {
} sa.resetTargets();
} else { if (tgt.canOnlyTgtOpponent()) {
List<Card> list = game.getCombat().getAttackers(); boolean canTgt = false;
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
list = CardLists.getTargetableCards(list, sa); for (Player opp2 : ai.getOpponents()) {
Card target = ComputerUtilCard.getBestCreatureAI(list); if (sa.canTarget(opp2)) {
if (target == null) { sa.getTargets().add(opp2);
return false; canTgt = true;
} break;
sa.getTargets().add(target); }
} }
}
randomReturn = true; if (!canTgt) {
} else if (logic.equals("ChainVeil")) { return false;
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) }
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) { } else {
return false; List<Card> list = game.getCombat().getAttackers();
} list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
randomReturn = true; list = CardLists.getTargetableCards(list, sa);
} else if (logic.equals("SpellCopy")) { Card target = ComputerUtilCard.getBestCreatureAI(list);
// fetch Instant or Sorcery and AI has reason to play this turn if (target == null) {
// does not try to get itself return false;
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() { }
@Override sa.getTargets().add(target);
public boolean apply(final Card c) { }
return (c.isInstant() || c.isSorcery()) && c != sa.getHostCard() && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c); }
} randomReturn = true;
}); } else if (logic.equals("ChainVeil")) {
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2)
if(count == 0) { || CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
return false; return false;
} }
randomReturn = true;
randomReturn = true; } else if (logic.equals("SpellCopy")) {
} else if (logic.equals("NarsetRebound")) { // fetch Instant or Sorcery and AI has reason to play this turn
// should be done in Main2, but it might broke for other cards // does not try to get itself
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) { final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
// return false; @Override
//} public boolean apply(final Card c) {
return (c.isInstant() || c.isSorcery()) && c != sa.getHostCard() && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn }
// only need count, not the list });
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override if(count == 0) {
public boolean apply(final Card c) { return false;
return (c.isInstant() || c.isSorcery()) && !c.hasKeyword("Rebound") && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c); }
}
}); randomReturn = true;
} else if (logic.equals("NarsetRebound")) {
if(count == 0) { // should be done in Main2, but it might broke for other cards
return false; //if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
} // return false;
//}
randomReturn = true;
} else if (logic.equals("Always")) { // fetch Instant or Sorcery without Rebound and AI has reason to play this turn
randomReturn = true; // only need count, not the list
} else if (logic.equals("Main2")) { final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
if (phase.getPhase().isBefore(PhaseType.MAIN2)) { @Override
return false; public boolean apply(final Card c) {
} return (c.isInstant() || c.isSorcery()) && !c.hasKeyword(Keyword.REBOUND)
randomReturn = true; && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
} else if (logic.equals("Evasion")) { }
});
if (!phase.isPlayerTurn(ai)) {
return false; if(count == 0) {
} return false;
}
boolean shouldPlay = false;
randomReturn = true;
List<Card> comp = ai.getCreaturesInPlay(); } else if (logic.equals("Always")) {
randomReturn = true;
for (final Player opp : ai.getOpponents()) { } else if (logic.equals("Main1")) {
List<Card> human = opp.getCreaturesInPlay(); if (phase.getPhase().isBefore(PhaseType.MAIN1)) {
return false;
// only count creatures that can attack or block }
comp = CardLists.filter(comp, new Predicate<Card>() { randomReturn = true;
@Override } else if (logic.equals("Main2")) {
public boolean apply(final Card c) { if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
return CombatUtil.canAttack(c, opp); return false;
} }
}); randomReturn = true;
if (comp.size() < 2) { } else if (logic.equals("Evasion")) {
continue;
} if (!phase.isPlayerTurn(ai)) {
final List<Card> attackers = comp; return false;
human = CardLists.filter(human, new Predicate<Card>() { }
@Override
public boolean apply(final Card c) { boolean shouldPlay = false;
return CombatUtil.canBlockAtLeastOne(c, attackers);
} List<Card> comp = ai.getCreaturesInPlay();
});
if (human.isEmpty()) { for (final Player opp : ai.getOpponents()) {
continue; List<Card> human = opp.getCreaturesInPlay();
}
// only count creatures that can attack or block
shouldPlay = true; comp = CardLists.filter(comp, new Predicate<Card>() {
break; @Override
} public boolean apply(final Card c) {
return CombatUtil.canAttack(c, opp);
if (shouldPlay) { }
return true; });
} if (comp.size() < 2) {
} else if (logic.equals("RedirectSpellDamageFromPlayer")) { continue;
if (game.getStack().isEmpty()) { }
return false; final List<Card> attackers = comp;
} human = CardLists.filter(human, new Predicate<Card>() {
boolean threatened = false; @Override
for (final SpellAbilityStackInstance stackInst : game.getStack()) { public boolean apply(final Card c) {
if (!stackInst.isSpell()) { continue; } return CombatUtil.canBlockAtLeastOne(c, attackers);
SpellAbility stackSpellAbility = stackInst.getSpellAbility(true); }
if (stackSpellAbility.getApi() == ApiType.DealDamage) { });
final SpellAbility saTargeting = stackSpellAbility.getSATargetingPlayer(); if (human.isEmpty()) {
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) { continue;
threatened = true; }
}
} shouldPlay = true;
} break;
randomReturn = threatened; }
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
if (game.getStack().isEmpty()) { return shouldPlay;
return false; } else if (logic.equals("RedirectSpellDamageFromPlayer")) {
} if (game.getStack().isEmpty()) {
final SpellAbility saTop = game.getStack().peekAbility(); return false;
final Card host = saTop.getHostCard(); }
if (saTop.getActivatingPlayer() != ai // from opponent boolean threatened = false;
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) // no prevent damage for (final SpellAbilityStackInstance stackInst : game.getStack()) {
&& host != null && (host.isInstant() || host.isSorcery()) if (!stackInst.isSpell()) { continue; }
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target SpellAbility stackSpellAbility = stackInst.getSpellAbility(true);
final ApiType type = saTop.getApi(); if (stackSpellAbility.getApi() == ApiType.DealDamage) {
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell final SpellAbility saTargeting = stackSpellAbility.getSATargetingPlayer();
sa.getTargets().add(host); if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
return true; threatened = true;
} }
} }
return false; }
} else if (logic.equals("NoGain")) { randomReturn = threatened;
// basic logic to cancel GainLife on stack } else if (logic.equals("Prevent")) { // prevent burn spell from opponent
if (game.getStack().isEmpty()) { if (game.getStack().isEmpty()) {
return false; return false;
} }
final SpellAbility topStack = game.getStack().peekAbility(); final SpellAbility saTop = game.getStack().peekAbility();
if (topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife) { final Card host = saTop.getHostCard();
return true; if (saTop.getActivatingPlayer() != ai // from opponent
} else { && !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) // no prevent damage
return false; && host != null && (host.isInstant() || host.isSorcery())
} && !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
} else if (logic.equals("Fight")) { final ApiType type = saTop.getApi();
return FightAi.canFightAi(ai, sa, 0, 0); if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
} else if (logic.equals("Burn")) { sa.getTargets().add(host);
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack) return true;
SpellAbility burn = sa.getSubAbility(); }
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn); }
} else if (logic.equals("YawgmothsWill")) { return false;
return SpecialCardAi.YawgmothsWill.consider(ai, sa); } else if (logic.equals("NoGain")) {
} else if (logic.startsWith("NeedCreatures")) { // basic logic to cancel GainLife on stack
if (ai.getCreaturesInPlay().isEmpty()) { if (game.getStack().isEmpty()) {
return false; return false;
} }
if (logic.contains(":")) { final SpellAbility topStack = game.getStack().peekAbility();
String k[] = logic.split(":"); if (topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife) {
Integer i = Integer.valueOf(k[1]); return true;
if (ai.getCreaturesInPlay().size() < i) { } else {
return false; return false;
} }
} } else if (logic.equals("Fight")) {
return true; return FightAi.canFightAi(ai, sa, 0, 0);
} } else if (logic.equals("Burn")) {
} else { //no AILogic // for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
return false; SpellAbility burn = sa.getSubAbility();
} return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
} else if (logic.equals("YawgmothsWill")) {
if ("False".equals(sa.getParam("Stackable"))) { return SpecialCardAi.YawgmothsWill.consider(ai, sa);
String name = sa.getParam("Name"); } else if (logic.startsWith("NeedCreatures")) {
if (name == null) { if (ai.getCreaturesInPlay().isEmpty()) {
name = sa.getHostCard().getName() + "'s Effect"; return false;
} }
if (sa.getActivatingPlayer().isCardInCommand(name)) { if (logic.contains(":")) {
return false; String k[] = logic.split(":");
} Integer i = Integer.valueOf(k[1]);
} if (ai.getCreaturesInPlay().size() < i) {
return false;
final TargetRestrictions tgt = sa.getTargetRestrictions(); }
if (tgt != null && tgt.canTgtPlayer()) { }
sa.resetTargets(); return true;
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) { } else if (logic.equals("CastFromGraveThisTurn")) {
boolean canTgt = false; CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
for (Player opp : ai.getOpponents()) { list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
if (sa.canTarget(opp)) { if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
sa.getTargets().add(opp); return false;
canTgt = true; }
break; }
} } else { //no AILogic
} return false;
return canTgt; }
} else {
sa.getTargets().add(ai); if ("False".equals(sa.getParam("Stackable"))) {
} String name = sa.getParam("Name");
} if (name == null) {
name = sa.getHostCard().getName() + "'s Effect";
return randomReturn; }
} if (sa.getActivatingPlayer().isCardInCommand(name)) {
} return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && tgt.canTgtPlayer()) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
boolean canTgt = false;
for (Player opp : ai.getOpponents()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
canTgt = true;
break;
}
}
return canTgt;
} else {
sa.getTargets().add(ai);
}
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
String aiLogic = sa.getParamOrDefault("AILogic", "");
// E.g. Nova Pentacle
if (aiLogic.equals("RedirectFromOppToCreature")) {
// try to target the opponent's best targetable permanent, if able
CardCollection oppPerms = CardLists.getValidCards(aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
if (!oppPerms.isEmpty()) {
sa.resetTargets();
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
return true;
}
if (mandatory) {
// try to target the AI's worst targetable permanent, if able
CardCollection aiPerms = CardLists.getValidCards(aiPlayer.getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
if (!aiPerms.isEmpty()) {
sa.resetTargets();
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
return true;
}
}
return false;
}
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
}
}

View File

@@ -1,124 +1,124 @@
/* /*
* Forge: Play Magic: the Gathering. * Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team * Copyright (C) 2011 Forge Team
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
/** /**
* <p> * <p>
* AbilityFactoryBond class. * AbilityFactoryBond class.
* </p> * </p>
* *
* @author Forge * @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $ * @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/ */
public final class EncodeAi extends SpellAbilityAi { public final class EncodeAi extends SpellAbilityAi {
/** /**
* <p> * <p>
* bondCanPlayAI. * bondCanPlayAI.
* </p> * </p>
* @param sa * @param sa
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.spellability.SpellAbility} object.
* @param af * @param af
* a {@link forge.game.ability.AbilityFactory} object. * a {@link forge.game.ability.AbilityFactory} object.
* *
* @return a boolean. * @return a boolean.
*/ */
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true; return true;
} }
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return true; return true;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player, * @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player,
* forge.game.spellability.SpellAbility, * forge.game.spellability.SpellAbility,
* forge.game.player.PlayerActionConfirmMode, java.lang.String) * forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */
@Override @Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// only try to encode if there is a creature it can be used on // only try to encode if there is a creature it can be used on
return chooseCard(player, player.getCreaturesInPlay(), true) != null; return chooseCard(player, player.getCreaturesInPlay(), true) != null;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, * @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean, * forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
* forge.game.player.Player) * forge.game.player.Player)
*/ */
@Override @Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return chooseCard(ai, options, isOptional); return chooseCard(ai, options, isOptional);
} }
private Card chooseCard(final Player ai, Iterable<Card> list, boolean isOptional) { private Card chooseCard(final Player ai, Iterable<Card> list, boolean isOptional) {
Card choice = null; Card choice = null;
// final String logic = sa.getParam("AILogic"); // final String logic = sa.getParam("AILogic");
// if (logic == null) { // if (logic == null) {
final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() { final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return ComputerUtilCombat.canAttackNextTurn(c); return ComputerUtilCombat.canAttackNextTurn(c);
} }
}); });
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() { final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
boolean canAttackOpponent = false; boolean canAttackOpponent = false;
for (Player opp : ai.getOpponents()) { for (Player opp : ai.getOpponents()) {
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) { if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) {
canAttackOpponent = true; canAttackOpponent = true;
break; break;
} }
} }
return canAttackOpponent; return canAttackOpponent;
} }
}); });
if (!unblockables.isEmpty()) { if (!unblockables.isEmpty()) {
choice = ComputerUtilCard.getBestAI(unblockables); choice = ComputerUtilCard.getBestAI(unblockables);
} else if (!attackers.isEmpty()) { } else if (!attackers.isEmpty()) {
choice = ComputerUtilCard.getBestAI(attackers); choice = ComputerUtilCard.getBestAI(attackers);
} else if (!isOptional) { } else if (!isOptional) {
choice = ComputerUtilCard.getBestAI(list); choice = ComputerUtilCard.getBestAI(list);
} }
// } // }
return choice; return choice;
} }
} }

View File

@@ -1,29 +1,29 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
* *
*/ */
public class EndTurnAi extends SpellAbilityAi { public class EndTurnAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory; return mandatory;
} }
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; } public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; return false;
} }
} }

View File

@@ -0,0 +1,63 @@
package forge.ai.ability;
import forge.ai.*;
import forge.game.card.*;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class ExploreAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// Explore with a target (e.g. Enter the Unknown)
if (sa.usesTargeting()) {
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiPlayer.getCardsIn(ZoneType.Battlefield));
if (bestCreature == null) {
return false;
}
sa.resetTargets();
sa.getTargets().add(bestCreature);
}
return true;
}
public static Card shouldPutInGraveyard(CardCollection top, Player ai) {
int predictedMana = ComputerUtilMana.getAvailableManaSources(ai, false).size();
CardCollectionView cardsOTB = ai.getCardsIn(ZoneType.Battlefield);
CardCollectionView cardsInHand = ai.getCardsIn(ZoneType.Hand);
CardCollection landsOTB = CardLists.filter(cardsOTB, CardPredicates.Presets.LANDS_PRODUCING_MANA);
CardCollection landsInHand = CardLists.filter(cardsInHand, CardPredicates.Presets.LANDS_PRODUCING_MANA);
int maxCMCDiff = 1;
int numLandsToStillNeedMore = 2;
if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
maxCMCDiff = aic.getIntProperty(AiProps.EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD);
numLandsToStillNeedMore = aic.getIntProperty(AiProps.EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE);
}
if (!top.isEmpty()) {
Card topCard = top.getFirst();
if (landsInHand.isEmpty() && landsOTB.size() <= numLandsToStillNeedMore) {
// We need more lands to improve our mana base, explore away the non-lands
return topCard;
}
if (topCard.getCMC() - maxCMCDiff >= predictedMana && !topCard.hasSVar("DoNotDiscardIfAble")) {
// We're not casting this in foreseeable future, put it in the graveyard
return topCard;
}
}
// Put on top of the library (do not mark the card for placement in the graveyard)
return null;
}
}

View File

@@ -1,262 +1,271 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import forge.ai.*;
import java.util.Map; import forge.game.ability.AbilityFactory;
import java.util.Random; import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.ai.ComputerUtil; import forge.game.card.CardCollection;
import forge.ai.ComputerUtilAbility; import forge.game.card.CardCollectionView;
import forge.ai.ComputerUtilCard; import forge.game.card.CardLists;
import forge.ai.ComputerUtilCombat; import forge.game.keyword.Keyword;
import forge.ai.SpellAbilityAi; import forge.game.player.Player;
import forge.game.ability.AbilityFactory; import forge.game.spellability.AbilitySub;
import forge.game.ability.AbilityUtils; import forge.game.spellability.SpellAbility;
import forge.game.card.Card; import forge.game.trigger.Trigger;
import forge.game.card.CardCollection; import forge.game.trigger.TriggerType;
import forge.game.card.CardCollectionView; import forge.util.MyRandom;
import forge.game.card.CardLists;
import forge.game.player.Player; import java.util.List;
import forge.game.spellability.AbilitySub; import java.util.Map;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; public class FightAi extends SpellAbilityAi {
import forge.game.trigger.TriggerType; @Override
import forge.util.MyRandom; protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if (sa.hasParam("FightWithToughness")) {
public class FightAi extends SpellAbilityAi { // TODO: add ailogic
@Override return false;
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { }
if (sa.hasParam("FightWithToughness")) { return super.checkAiLogic(ai, sa, aiLogic);
// TODO: add ailogic }
return false;
} @Override
return super.checkAiLogic(ai, sa, aiLogic); protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
} sa.resetTargets();
final Card source = sa.getHostCard();
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { // everything is defined or targeted above, can't do anything there?
sa.resetTargets(); if (sa.hasParam("Defined") && !sa.usesTargeting()) {
final Card source = sa.getHostCard(); // TODO extend Logic for cards like Arena or Grothama
return true;
// Get creature lists }
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa); // Get creature lists
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures); CardCollectionView aiCreatures = ai.getCreaturesInPlay();
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay(); aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
humCreatures = CardLists.getTargetableCards(humCreatures, sa); aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
// assumes the triggered card belongs to the ai humCreatures = CardLists.getTargetableCards(humCreatures, sa);
if (sa.hasParam("Defined")) {
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0); // assumes the triggered card belongs to the ai
for (Card humanCreature : humCreatures) { if (sa.hasParam("Defined")) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower() Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) { for (Card humanCreature : humCreatures) {
// todo: check min/max targets; see if we picked the best if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
// matchup && humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
sa.getTargets().add(humanCreature); // todo: check min/max targets; see if we picked the best
return true; // matchup
} else if (humanCreature.getSVar("Targeting").equals("Dies")) { sa.getTargets().add(humanCreature);
sa.getTargets().add(humanCreature); return true;
return true; } else if (humanCreature.getSVar("Targeting").equals("Dies")) {
} sa.getTargets().add(humanCreature);
} return true;
} }
}
if (sa.hasParam("TargetsFromDifferentZone")) { return false; // bail at this point, otherwise the AI will overtarget and waste the activation
if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) { }
for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) { if (sa.hasParam("TargetsFromDifferentZone")) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower() if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) {
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) { for (Card humanCreature : humCreatures) {
// todo: check min/max targets; see if we picked the for (Card aiCreature : aiCreatures) {
// best matchup if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
sa.getTargets().add(humanCreature); && humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
sa.getTargets().add(aiCreature); // todo: check min/max targets; see if we picked the
return true; // best matchup
} else if (humanCreature.getSVar("Targeting").equals("Dies")) { sa.getTargets().add(humanCreature);
sa.getTargets().add(humanCreature); sa.getTargets().add(aiCreature);
sa.getTargets().add(aiCreature); return true;
return true; } else if (humanCreature.getSVar("Targeting").equals("Dies")) {
} sa.getTargets().add(humanCreature);
} sa.getTargets().add(aiCreature);
} return true;
} }
return false; }
} }
for (Card creature1 : humCreatures) { }
for (Card creature2 : humCreatures) { return false;
if (creature1.equals(creature2)) { }
continue; for (Card creature1 : humCreatures) {
} for (Card creature2 : humCreatures) {
if (sa.hasParam("TargetsWithoutSameCreatureType") && creature1.sharesCreatureTypeWith(creature2)) { if (creature1.equals(creature2)) {
continue; continue;
} }
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetPower() if (sa.hasParam("TargetsWithoutSameCreatureType") && creature1.sharesCreatureTypeWith(creature2)) {
&& creature1.getNetPower() >= ComputerUtilCombat.getDamageToKill(creature2)) { continue;
// todo: check min/max targets; see if we picked the best }
// matchup if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetPower()
sa.getTargets().add(creature1); && creature1.getNetPower() >= ComputerUtilCombat.getDamageToKill(creature2)) {
sa.getTargets().add(creature2); // todo: check min/max targets; see if we picked the best
return true; // matchup
} sa.getTargets().add(creature1);
} sa.getTargets().add(creature2);
} return true;
return false; }
} }
}
@Override return false;
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { }
if (canPlayAI(ai, sa)) {
return true; @Override
} public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
if (!mandatory) { return checkApiLogic(aiPlayer, sa);
return false; }
}
@Override
//try to make a good trade or no trade protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard(); if (canPlayAI(ai, sa)) {
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay(); return true;
humCreatures = CardLists.getTargetableCards(humCreatures, sa); }
if (humCreatures.isEmpty()) { if (!mandatory) {
return false; return false;
} }
//assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) { //try to make a good trade or no trade
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0); final Card source = sa.getHostCard();
for (Card humanCreature : humCreatures) { List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower() humCreatures = CardLists.getTargetableCards(humCreatures, sa);
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) { if (humCreatures.isEmpty()) {
sa.getTargets().add(humanCreature); return false;
return true; }
} //assumes the triggered card belongs to the ai
} if (sa.hasParam("Defined")) {
for (Card humanCreature : humCreatures) { Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetPower()) { for (Card humanCreature : humCreatures) {
sa.getTargets().add(humanCreature); if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
return true; && ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
} sa.getTargets().add(humanCreature);
} return true;
sa.getTargets().add(humCreatures.get(0)); }
return true; }
} for (Card humanCreature : humCreatures) {
return true; if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetPower()) {
} sa.getTargets().add(humanCreature);
return true;
/** }
* Logic for evaluating fight effects }
* @param ai controlling player sa.getTargets().add(humCreatures.get(0));
* @param sa host SpellAbility return true;
* @param toughness bonus to toughness }
* @param power bonus to power return true;
* @return true if fight effect should be played, false otherwise }
*/
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) { /**
final Card source = sa.getHostCard(); * Logic for evaluating fight effects
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); * @param ai controlling player
final AbilitySub tgtFight = sa.getSubAbility(); * @param sa host SpellAbility
final boolean isChandrasIgnition = "Chandra's Ignition".equals(sourceName); // TODO: generalize this for other "fake Fight" cases that do not target * @param toughness bonus to toughness
if ("Savage Punch".equals(sourceName) && !ai.hasFerocious()) { * @param power bonus to power
power = 0; * @return true if fight effect should be played, false otherwise
toughness = 0; */
} public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
// Get sorted creature lists final Card source = sa.getHostCard();
CardCollection aiCreatures = ai.getCreaturesInPlay(); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay(); final AbilitySub tgtFight = sa.getSubAbility();
if ("Time to Feed".equals(sourceName)) { // flip sa final boolean isChandrasIgnition = "Chandra's Ignition".equals(sourceName); // TODO: generalize this for other "fake Fight" cases that do not target
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight); if ("Savage Punch".equals(sourceName) && !ai.hasFerocious()) {
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures); power = 0;
humCreatures = CardLists.getTargetableCards(humCreatures, sa); toughness = 0;
} else { }
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa); // Get sorted creature lists
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures); CardCollection aiCreatures = ai.getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight); CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
} if ("Time to Feed".equals(sourceName)) { // flip sa
ComputerUtilCard.sortByEvaluateCreature(aiCreatures); aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
ComputerUtilCard.sortByEvaluateCreature(humCreatures); aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) { humCreatures = CardLists.getTargetableCards(humCreatures, sa);
return false; } else {
} aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
// Evaluate creature pairs aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
for (Card humanCreature : humCreatures) { humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
for (Card aiCreature : aiCreatures) { }
if (source.isSpell()) { // heroic triggers adding counters and prowess ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
final int bonus = getSpellBonus(aiCreature); ComputerUtilCard.sortByEvaluateCreature(humCreatures);
power += bonus; if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
toughness += bonus; return false;
} }
if ("PowerDmg".equals(sa.getParam("AILogic"))) { // Evaluate creature pairs
if (FightAi.canKill(aiCreature, humanCreature, power)) { for (Card humanCreature : humCreatures) {
sa.getTargets().add(aiCreature); for (Card aiCreature : aiCreatures) {
if (!isChandrasIgnition) { if (source.isSpell()) { // heroic triggers adding counters and prowess
tgtFight.resetTargets(); final int bonus = getSpellBonus(aiCreature);
tgtFight.getTargets().add(humanCreature); power += bonus;
} toughness += bonus;
return true; }
} if ("PowerDmg".equals(sa.getParam("AILogic"))) {
} else { if (FightAi.canKill(aiCreature, humanCreature, power)) {
if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) { sa.getTargets().add(aiCreature);
if ("Time to Feed".equals(sourceName)) { // flip targets if (!isChandrasIgnition) {
final Card tmp = aiCreature; tgtFight.resetTargets();
aiCreature = humanCreature; tgtFight.getTargets().add(humanCreature);
humanCreature = tmp; }
} return true;
sa.getTargets().add(aiCreature); }
tgtFight.resetTargets(); } else {
tgtFight.getTargets().add(humanCreature); if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) {
return true; if ("Time to Feed".equals(sourceName)) { // flip targets
} final Card tmp = aiCreature;
} aiCreature = humanCreature;
} humanCreature = tmp;
} }
return false; sa.getTargets().add(aiCreature);
} tgtFight.resetTargets();
tgtFight.getTargets().add(humanCreature);
/** return true;
* Compute the bonus from Heroic +1/+1 counters or Prowess }
*/ }
private static int getSpellBonus(final Card aiCreature) { }
for (Trigger t : aiCreature.getTriggers()) { }
if (t.getMode() == TriggerType.SpellCast) { return false;
final Map<String, String> params = t.getMapParams(); }
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
&& params.containsKey("Execute")) { /**
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature); * Compute the bonus from Heroic +1/+1 counters or Prowess
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) { */
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic); private static int getSpellBonus(final Card aiCreature) {
} for (Trigger t : aiCreature.getTriggers()) {
break; if (t.getMode() == TriggerType.SpellCast) {
} final Map<String, String> params = t.getMapParams();
if ("ProwessPump".equals(params.get("Execute"))) { if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
return 1; && params.containsKey("Execute")) {
} SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
} if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
} return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
return 0; }
} break;
}
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) { if ("ProwessPump".equals(params.get("Execute"))) {
if (canKill(fighter, opponent, pumpAttack)) { return 1;
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive }
return true; }
} else { }
final Random r = MyRandom.getRandom(); return 0;
if (r.nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade }
return true;
} private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
} if (canKill(fighter, opponent, pumpAttack)) {
} if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
return false; return true;
} } else {
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) { if (MyRandom.getRandom().nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
if (opponent.getSVar("Targeting").equals("Dies")) { return true;
return true; }
} }
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed() }
|| opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) { return false;
return false; }
}
if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) { public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
return true; if (opponent.getSVar("Targeting").equals("Dies")) {
} return true;
return false; }
} if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed() || opponent.getShieldCount() > 0
} || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
return false;
}
if (fighter.hasKeyword(Keyword.DEATHTOUCH)
|| ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
return true;
}
return false;
}
}

View File

@@ -1,47 +1,47 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class FlipACoinAi extends SpellAbilityAi { public class FlipACoinAi extends SpellAbilityAi {
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (sa.hasParam("AILogic")) { if (sa.hasParam("AILogic")) {
String AILogic = sa.getParam("AILogic"); String AILogic = sa.getParam("AILogic");
if (AILogic.equals("Never")) { if (AILogic.equals("Never")) {
return false; return false;
} else if (AILogic.equals("PhaseOut")) { } else if (AILogic.equals("PhaseOut")) {
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) { if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
return false; return false;
} }
} else if (AILogic.equals("KillOrcs")) { } else if (AILogic.equals("KillOrcs")) {
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) { if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
return false; return false;
} }
sa.resetTargets(); sa.resetTargets();
for (Card c : ai.getOpponents().getCreaturesInPlay()) { for (Card c : ai.getOpponents().getCreaturesInPlay()) {
if (sa.canTarget(c)) { if (sa.canTarget(c)) {
sa.getTargets().add(c); sa.getTargets().add(c);
return true; return true;
} }
} }
return false; return false;
} }
} }
return true; return true;
} }
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa); return canPlayAI(ai, sa);
} }
} }

View File

@@ -1,69 +1,116 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtilCombat; import forge.ai.*;
import forge.ai.SpellAbilityAi; import forge.game.Game;
import forge.game.Game; import forge.game.GameObject;
import forge.game.phase.PhaseType; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.card.CardPredicates;
import forge.game.spellability.SpellAbility; import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
public class FogAi extends SpellAbilityAi { import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/* (non-Javadoc) import forge.util.Aggregates;
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/ import java.util.List;
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { public class FogAi extends SpellAbilityAi {
final Game game = ai.getGame();
// AI should only activate this during Human's Declare Blockers phase /* (non-Javadoc)
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) { * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
return false; */
} @Override
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
return false; final Game game = ai.getGame();
} final Card hostCard = sa.getHostCard();
// Only cast when Stack is empty, so Human uses spells/abilities first // Don't cast it, if the effect is already in place
if (!game.getStack().isEmpty()) { if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false; return false;
} }
// Don't cast it, if the effect is already in place // if card would be destroyed, react and use immediately if it's not own turn
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) { if ((AiCardMemory.isRememberedCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
return false; && (!game.getStack().isEmpty())
} && (!game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()))) {
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, null);
// Cast it if life is in danger if (objects.contains(hostCard)) {
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat()); AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
} return true;
}
@Override }
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI should only activate this during Human's turn // Reserve mana to cast this card if it will be likely needed
boolean chance; if (((game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()))
final Game game = ai.getGame(); || (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)))
&& (AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
// should really check if other player is attacking this player && (ComputerUtil.aiLifeInDanger(ai, false, 0))) {
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) { ((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, true);
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE); AiCardMemory.rememberCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
} else { }
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
} // AI should only activate this during Human's Declare Blockers phase
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return chance; return false;
} }
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
@Override return false;
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { }
final Game game = aiPlayer.getGame();
boolean chance; // Only cast when Stack is empty, so Human uses spells/abilities first
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer().getOpponent())) { if (!game.getStack().isEmpty()) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE); return false;
} else { }
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
} if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
int dmg = 0;
return chance; for (Card atk : game.getCombat().getAttackersOf(ai)) {
} if (game.getCombat().isUnblocked(atk)) {
} dmg += atk.getNetCombatDamage();
} else if (atk.hasKeyword(Keyword.TRAMPLE)) {
dmg += atk.getNetCombatDamage() - Aggregates.sum(game.getCombat().getBlockers(atk), CardPredicates.Accessors.fnGetNetToughness);
}
}
if (dmg > ai.getLife() / 4) {
return true;
} else if (dmg >= 5) {
return true;
} else if (ai.getLife() < ai.getStartingLife() / 3) {
return true;
}
}
// Cast it if life is in danger
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI should only activate this during Human's turn
boolean chance;
final Game game = ai.getGame();
// should really check if other player is attacking this player
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Game game = aiPlayer.getGame();
boolean chance;
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);
}
return chance;
}
}

View File

@@ -1,49 +1,50 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtil;
import forge.game.player.Player; import forge.ai.SpellAbilityAi;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class GameLossAi extends SpellAbilityAi {
@Override public class GameLossAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) { @Override
final Player opp = ai.getOpponent(); protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (opp.cantLose()) { final Player opp = ComputerUtil.getOpponentFor(ai);
return false; if (opp.cantLose()) {
} return false;
}
// Only one SA Lose the Game card right now, which is Door to
// Nothingness // Only one SA Lose the Game card right now, which is Door to
// Nothingness
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets(); if (tgt != null) {
sa.getTargets().add(opp); sa.resetTargets();
} sa.getTargets().add(opp);
}
// In general, don't return true.
// But this card wins the game, I can make an exception for that // In general, don't return true.
return true; // But this card wins the game, I can make an exception for that
} return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a // Phage the Untouchable
// specific turn, which can't be done yet) // (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()) {
return false; if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
} return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets(); if (tgt != null) {
sa.getTargets().add(ai.getOpponent()); sa.resetTargets();
} sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return true;
} return true;
} }
}

View File

@@ -1,32 +1,32 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class GameWinAi extends SpellAbilityAi { public class GameWinAi extends SpellAbilityAi {
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (ai.cantWin()) { if (ai.cantWin()) {
return false; return false;
} }
// TODO Check conditions are met on card (e.g. Coalition Victory) // TODO Check conditions are met on card (e.g. Coalition Victory)
// TODO Consider likelihood of SA getting countered // TODO Consider likelihood of SA getting countered
// In general, don't return true. // In general, don't return true.
// But this card wins the game, I can make an exception for that // But this card wins the game, I can make an exception for that
return true; return true;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true; return true;
} }
} }

View File

@@ -1,35 +1,35 @@
package forge.ai.ability; package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public class HauntAi extends SpellAbilityAi { public class HauntAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Game game = ai.getGame(); final Game game = ai.getGame();
if (sa.usesTargeting() && !card.isToken()) { if (sa.usesTargeting() && !card.isToken()) {
final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.CREATURES); CardPredicates.Presets.CREATURES);
// nothing to haunt // nothing to haunt
if (creats.isEmpty()) { if (creats.isEmpty()) {
return false; return false;
} }
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents()); final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats)); sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats));
} }
return true; return true;
} }
} }

View File

@@ -1,60 +1,60 @@
package forge.ai.ability; package forge.ai.ability;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
* *
*/ */
public class LegendaryRuleAi extends SpellAbilityAi { public class LegendaryRuleAi extends SpellAbilityAi {
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility) * @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/ */
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here return false; // should not get here
} }
@Override @Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) { public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
// Choose a single legendary/planeswalker card to keep // Choose a single legendary/planeswalker card to keep
Card firstOption = Iterables.getFirst(options, null); Card firstOption = Iterables.getFirst(options, null);
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker(); boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
if ( choosingFromPlanewalkers ) { if ( choosingFromPlanewalkers ) {
// AI decision making - should AI compare counters? // AI decision making - should AI compare counters?
} else { } else {
// AI decision making - should AI compare damage and debuffs? // AI decision making - should AI compare damage and debuffs?
} }
// TODO: Can this be made more generic somehow? // TODO: Can this be made more generic somehow?
if (firstOption.getName().equals("Dark Depths")) { if (firstOption.getName().equals("Dark Depths")) {
Card best = firstOption; Card best = firstOption;
for (Card c : options) { for (Card c : options) {
if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) { if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
best = c; best = c;
} }
} }
return best; return best;
} else if (firstOption.getCounters(CounterType.KI) > 0) { } else if (firstOption.getCounters(CounterType.KI) > 0) {
// Extra Rule for KI counter // Extra Rule for KI counter
Card best = firstOption; Card best = firstOption;
for (Card c : options) { for (Card c : options) {
if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) { if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
best = c; best = c;
} }
} }
return best; return best;
} }
return firstOption; return firstOption;
} }
} }

View File

@@ -1,93 +1,91 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.SpellAbilityAi; import forge.ai.ComputerUtil;
import forge.game.player.Player; import forge.ai.SpellAbilityAi;
import forge.game.spellability.SpellAbility; import forge.game.player.Player;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.SpellAbility;
import forge.util.MyRandom; import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.Random;
public class LifeExchangeAi extends SpellAbilityAi {
public class LifeExchangeAi extends SpellAbilityAi {
/*
/* * (non-Javadoc)
* (non-Javadoc) *
* * @see
* @see * forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI * (forge.game.player.Player, java.util.Map,
* (forge.game.player.Player, java.util.Map, * forge.card.spellability.SpellAbility)
* forge.card.spellability.SpellAbility) */
*/ @Override
@Override protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { final int myLife = aiPlayer.getLife();
final Random r = MyRandom.getRandom(); Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
final int myLife = aiPlayer.getLife(); final int hLife = opponent.getLife();
Player opponent = aiPlayer.getOpponent();
final int hLife = opponent.getLife(); if (!aiPlayer.canGainLife()) {
return false;
if (!aiPlayer.canGainLife()) { }
return false;
} // prevent run-away activations - first time will always return true
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); /*
* TODO - There is one card that takes two targets (Soul Conduit)
/* * and one card that has a conditional (Psychic Transfer) that are
* TODO - There is one card that takes two targets (Soul Conduit) * not currently handled
* and one card that has a conditional (Psychic Transfer) that are */
* not currently handled final TargetRestrictions tgt = sa.getTargetRestrictions();
*/ if (tgt != null) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); sa.resetTargets();
if (tgt != null) { if (opponent.canBeTargetedBy(sa)) {
sa.resetTargets(); // never target self, that would be silly for exchange
if (opponent.canBeTargetedBy(sa)) { sa.getTargets().add(opponent);
// never target self, that would be silly for exchange if (!opponent.canLoseLife()) {
sa.getTargets().add(opponent); return false;
if (!opponent.canLoseLife()) { }
return false; }
} }
}
} // if life is in danger, always activate
if ((myLife < 5) && (hLife > myLife)) {
// if life is in danger, always activate return true;
if ((myLife < 5) && (hLife > myLife)) { }
return true;
} // cost includes sacrifice probably, so make sure it's worth it
chance &= (hLife > (myLife + 8));
// cost includes sacrifice probably, so make sure it's worth it
chance &= (hLife > (myLife + 8)); return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
}
return ((r.nextFloat() < .6667) && chance);
} /**
* <p>
/** * exchangeLifeDoTriggerAINoCost.
* <p> * </p>
* exchangeLifeDoTriggerAINoCost. * @param sa
* </p> * a {@link forge.game.spellability.SpellAbility} object.
* @param sa * @param mandatory
* a {@link forge.game.spellability.SpellAbility} object. * a boolean.
* @param mandatory * @param af
* a boolean. * a {@link forge.game.ability.AbilityFactory} object.
* @param af *
* a {@link forge.game.ability.AbilityFactory} object. * @return a boolean.
* */
* @return a boolean. @Override
*/ protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
@Override final boolean mandatory) {
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) { final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ComputerUtil.getOpponentFor(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt != null) {
Player opp = ai.getOpponent(); sa.resetTargets();
if (tgt != null) { if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
sa.resetTargets(); sa.getTargets().add(opp);
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) { } else {
sa.getTargets().add(opp); return false;
} else { }
return false; }
} return true;
} }
return true;
} }
}

View File

@@ -0,0 +1,141 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.MagicStack;
public class LifeExchangeVariantAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
* (forge.game.player.Player, java.util.Map,
* forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame();
if ("Tree of Redemption".equals(sourceName)) {
if (!ai.canGainLife())
return false;
// someone controls "Rain of Gore" or "Sulfuric Vortex", lifegain is bad in that case
if (game.isCardInPlay("Rain of Gore") || game.isCardInPlay("Sulfuric Vortex"))
return false;
// an opponent controls "Tainted Remedy", lifegain is bad in that case
for (Player op : ai.getOpponents()) {
if (op.isCardInPlay("Tainted Remedy"))
return false;
}
if (ComputerUtil.waitForBlocking(sa) || ai.getLife() + 1 >= source.getNetToughness()
|| (ai.getLife() > 5 && !ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
return false;
}
}
else if ("Tree of Perdition".equals(sourceName)) {
boolean shouldDo = false;
if (ComputerUtil.waitForBlocking(sa))
return false;
for (Player op : ai.getOpponents()) {
// if oppoent can't be targeted, or it can't lose life, try another one
if (!op.canBeTargetedBy(sa) || !op.canLoseLife())
continue;
// an opponent has more live than this toughness
if (op.getLife() + 1 >= source.getNetToughness()) {
shouldDo = true;
} else {
// opponent can't gain life, so "Tainted Remedy" should not work.
if (!op.canGainLife()) {
continue;
} else if (ai.isCardInPlay("Tainted Remedy")) { // or AI has Tainted Remedy
shouldDo = true;
} else {
for (Player ally : ai.getAllies()) {
// if an Ally has Tainted Remedy and opponent is also opponent of ally
if (ally.isCardInPlay("Tainted Remedy") && op.isOpponentOf(ally))
shouldDo = true;
}
}
}
if (shouldDo) {
sa.getTargets().add(op);
break;
}
}
return shouldDo;
}
else if ("Evra, Halcyon Witness".equals(sourceName)) {
if (!ai.canGainLife())
return false;
int aiLife = ai.getLife();
if (source.getNetPower() > aiLife) {
if (ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat())) {
return true;
}
// check the top of stack
MagicStack stack = ai.getGame().getStack();
if (!stack.isEmpty()) {
SpellAbility saTop = stack.peekAbility();
if (ComputerUtil.predictDamageFromSpell(saTop, ai) >= aiLife) {
return true;
}
}
}
}
return false;
}
/**
* <p>
* exchangeLifeDoTriggerAINoCost.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ComputerUtil.getOpponentFor(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
sa.getTargets().add(opp);
} else {
return false;
}
}
return true;
}
}

View File

@@ -1,293 +1,313 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.*; import com.google.common.collect.Iterables;
import forge.game.Game; import forge.ai.*;
import forge.game.ability.AbilityUtils; import forge.game.Game;
import forge.game.card.Card; import forge.game.ability.AbilityUtils;
import forge.game.cost.Cost; import forge.game.card.Card;
import forge.game.phase.PhaseHandler; import forge.game.cost.Cost;
import forge.game.phase.PhaseType; import forge.game.cost.CostRemoveCounter;
import forge.game.player.Player; import forge.game.cost.CostSacrifice;
import forge.game.player.PlayerCollection; import forge.game.phase.PhaseHandler;
import forge.game.player.PlayerPredicates; import forge.game.phase.PhaseType;
import forge.game.spellability.AbilitySub; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.player.PlayerCollection;
import forge.util.MyRandom; import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
public class LifeGainAi extends SpellAbilityAi { import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
/*
* (non-Javadoc) public class LifeGainAi extends SpellAbilityAi {
*
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player, /*
* forge.game.spellability.SpellAbility, forge.game.cost.Cost, * (non-Javadoc)
* forge.game.card.Card) *
*/ * @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
@Override * forge.game.spellability.SpellAbility, forge.game.cost.Cost,
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) { * forge.game.card.Card)
final Game game = source.getGame(); */
final PhaseHandler ph = game.getPhaseHandler(); @Override
final int life = ai.getLife(); protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
final Game game = source.getGame();
boolean lifeCritical = life <= 5; final PhaseHandler ph = game.getPhaseHandler();
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE) final int life = ai.getLife();
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
boolean lifeCritical = life <= 5;
if (!lifeCritical) { lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
// return super.willPayCosts(ai, sa, cost, source); && ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, false)) {
return false; if (!lifeCritical) {
} // return super.willPayCosts(ai, sa, cost, source);
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) { if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
return false; return false;
} }
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) { return false;
return false; }
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) { return false;
return false; }
}
} else { if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
// don't sac possible blockers return false;
if (!ph.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS) }
|| !game.getCombat().getDefenders().contains(ai)) { } else {
boolean skipCheck = false; // don't sac possible blockers
// if it's a sac self cost and the effect source is not a if (!ph.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
// creature, skip this check || !game.getCombat().getDefenders().contains(ai)) {
// (e.g. Woodweaver's Puzzleknot) boolean skipCheck = false;
skipCheck |= ComputerUtilCost.isSacrificeSelfCost(cost) && !source.isCreature(); // if it's a sac self cost and the effect source is not a
// creature, skip this check
if (!skipCheck) { // (e.g. Woodweaver's Puzzleknot)
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, false)) { skipCheck |= ComputerUtilCost.isSacrificeSelfCost(cost) && !source.isCreature();
return false;
} if (!skipCheck) {
} if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
} return false;
} }
}
return true; }
} }
@Override return true;
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { }
final Game game = ai.getGame();
final int life = ai.getLife(); @Override
boolean activateForCost = ComputerUtil.activateForCost(sa, ai); protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Game game = ai.getGame();
boolean lifeCritical = life <= 5; final int life = ai.getLife();
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE) boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
boolean lifeCritical = life <= 5;
// Don't use lifegain before main 2 if possible lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false; // When life is critical but there is no immediate danger, try to wait until declare blockers
} // before using the lifegain ability if it's an ability on a creature with a detrimental activation cost
if (lifeCritical
if (!lifeCritical && !activateForCost && sa.isAbility()
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) && sa.getHostCard() != null && sa.getHostCard().isCreature()
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) { && sa.getPayCosts() != null
return false; && (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
} if (!game.getStack().isEmpty()) {
SpellAbility saTop = game.getStack().peekAbility();
return true; if (saTop.getTargets() != null && Iterables.contains(saTop.getTargets().getTargetPlayers(), ai)) {
} return ComputerUtil.predictDamageFromSpell(saTop, ai) > 0;
}
/* }
* (non-Javadoc) if (game.getCombat() == null) { return false; }
* if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; }
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, }
* forge.game.spellability.SpellAbility)
*/ // Don't use lifegain before main 2 if possible
@Override if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
protected boolean checkApiLogic(Player ai, SpellAbility sa) { && !ComputerUtil.castSpellInMain1(ai, sa)) {
final Card source = sa.getHostCard(); return false;
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); }
final int life = ai.getLife(); if (!lifeCritical && !activateForCost
final String amountStr = sa.getParam("LifeAmount"); && (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
int lifeAmount = 0; && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
boolean activateForCost = ComputerUtil.activateForCost(sa, ai); return false;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { }
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); return true;
source.setSVar("PayX", Integer.toString(xPay)); }
lifeAmount = xPay;
} else { /*
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); * (non-Javadoc)
} *
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
// Ugin AI: always use ultimate * forge.game.spellability.SpellAbility)
if (sourceName.equals("Ugin, the Spirit Dragon")) { */
// TODO: somehow link with DamageDealAi for cases where +1 = win @Override
return true; protected boolean checkApiLogic(Player ai, SpellAbility sa) {
} final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
// don't use it if no life to gain
if (!activateForCost && lifeAmount <= 0) { final int life = ai.getLife();
return false; final String amountStr = sa.getParam("LifeAmount");
} int lifeAmount = 0;
// don't play if the conditions aren't met, unless it would trigger a boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
// beneficial sub-condition if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
if (!activateForCost && !sa.getConditions().areMet(sa)) { // Set PayX here to maximum value.
final AbilitySub abSub = sa.getSubAbility(); final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) { source.setSVar("PayX", Integer.toString(xPay));
if (!abSub.getConditions().areMet(abSub)) { lifeAmount = xPay;
return false; } else {
} lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
} else { }
return false;
} // Ugin AI: always use ultimate
} if (sourceName.equals("Ugin, the Spirit Dragon")) {
// TODO: somehow link with DamageDealAi for cases where +1 = win
if (!activateForCost && !ai.canGainLife()) { return true;
return false; }
}
// don't use it if no life to gain
// prevent run-away activations - first time will always return true if (!activateForCost && lifeAmount <= 0) {
if (ComputerUtil.preventRunAwayActivations(sa)) { return false;
return false; }
} // don't play if the conditions aren't met, unless it would trigger a
// beneficial sub-condition
if (sa.usesTargeting()) { if (!activateForCost && !sa.getConditions().areMet(sa)) {
if (!target(ai, sa, true)) { final AbilitySub abSub = sa.getSubAbility();
return false; if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
} if (!abSub.getConditions().areMet(abSub)) {
} return false;
}
if (ComputerUtil.playImmediately(ai, sa)) { } else {
return true; return false;
} }
}
if (SpellAbilityAi.isSorcerySpeed(sa)
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) { if (!activateForCost && !ai.canGainLife()) {
return true; return false;
} }
// Save instant-speed life-gain unless it is really worth it // prevent run-away activations - first time will always return true
final float value = 0.9f * lifeAmount / life; if (ComputerUtil.preventRunAwayActivations(sa)) {
if (value < 0.2f) { return false;
return false; }
}
return MyRandom.getRandom().nextFloat() < value; if (sa.usesTargeting()) {
} if (!target(ai, sa, true)) {
return false;
/** }
* <p> }
* gainLifeDoTriggerAINoCost.
* </p> if (ComputerUtil.playImmediately(ai, sa)) {
* @param sa return true;
* a {@link forge.game.spellability.SpellAbility} object. }
* @param mandatory
* a boolean. if (SpellAbilityAi.isSorcerySpeed(sa)
* || sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
* @return a boolean. return true;
*/ }
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, // Save instant-speed life-gain unless it is really worth it
final boolean mandatory) { final float value = 0.9f * lifeAmount / life;
if (value < 0.2f) {
// If the Target is gaining life, target self. return false;
// if the Target is modifying how much life is gained, this needs to be }
// handled better return MyRandom.getRandom().nextFloat() < value;
if (sa.usesTargeting()) { }
if (!target(ai, sa, mandatory)) {
return false; /**
} * <p>
} * gainLifeDoTriggerAINoCost.
* </p>
final Card source = sa.getHostCard(); * @param sa
final String amountStr = sa.getParam("LifeAmount"); * a {@link forge.game.spellability.SpellAbility} object.
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { * @param mandatory
// Set PayX here to maximum value. * a boolean.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); *
source.setSVar("PayX", Integer.toString(xPay)); * @return a boolean.
} */
@Override
return true; protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
} final boolean mandatory) {
@Override // If the Target is gaining life, target self.
public boolean chkAIDrawback(SpellAbility sa, Player ai) { // if the Target is modifying how much life is gained, this needs to be
return doTriggerAINoCost(ai, sa, true); // handled better
} if (sa.usesTargeting()) {
if (!target(ai, sa, mandatory)) {
private boolean target(Player ai, SpellAbility sa, boolean mandatory) { return false;
Card source = sa.getHostCard(); }
sa.resetTargets(); }
// TODO : add add even more logic into it
// try to target opponents first if that would kill them final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa)); if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa)); // Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (sa.canTarget(ai) && ComputerUtil.lifegainPositive(ai, source)) { source.setSVar("PayX", Integer.toString(xPay));
sa.getTargets().add(ai); }
} else {
boolean hasTgt = false; return true;
// check for Lifegain negative on opponents }
for (Player opp : opps) {
if (ComputerUtil.lifegainNegative(opp, source)) { @Override
sa.getTargets().add(opp); public boolean chkAIDrawback(SpellAbility sa, Player ai) {
hasTgt = true; return doTriggerAINoCost(ai, sa, true);
break; }
}
} private boolean target(Player ai, SpellAbility sa, boolean mandatory) {
if (!hasTgt) { Card source = sa.getHostCard();
// lifegain on ally sa.resetTargets();
for (Player ally : allies) { // TODO : add add even more logic into it
if (ComputerUtil.lifegainPositive(ally, source)) { // try to target opponents first if that would kill them
sa.getTargets().add(ally);
hasTgt = true; PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
break; PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
}
} if (sa.canTarget(ai) && ComputerUtil.lifegainPositive(ai, source)) {
} sa.getTargets().add(ai);
if (!hasTgt && mandatory) { } else {
// need to target something but its neither negative against boolean hasTgt = false;
// opponents, // check for Lifegain negative on opponents
// nor posive against allies for (Player opp : opps) {
if (ComputerUtil.lifegainNegative(opp, source)) {
// hurting ally is probably better than healing opponent sa.getTargets().add(opp);
// look for Lifegain not Negative (case of lifegain negated) hasTgt = true;
for (Player ally : allies) { break;
if (!ComputerUtil.lifegainNegative(ally, source)) { }
sa.getTargets().add(ally); }
hasTgt = true; if (!hasTgt) {
break; // lifegain on ally
} for (Player ally : allies) {
} if (ComputerUtil.lifegainPositive(ally, source)) {
if (!hasTgt) { sa.getTargets().add(ally);
// same for opponent lifegain not positive hasTgt = true;
for (Player opp : opps) { break;
if (!ComputerUtil.lifegainPositive(opp, source)) { }
sa.getTargets().add(opp); }
hasTgt = true; }
break; if (!hasTgt && mandatory) {
} // need to target something but its neither negative against
} // opponents,
} // nor posive against allies
// still no luck, try to target ally with most life // hurting ally is probably better than healing opponent
if (!allies.isEmpty()) { // look for Lifegain not Negative (case of lifegain negated)
Player ally = allies.max(PlayerPredicates.compareByLife()); for (Player ally : allies) {
sa.getTargets().add(ally); if (!ComputerUtil.lifegainNegative(ally, source)) {
hasTgt = true; sa.getTargets().add(ally);
} hasTgt = true;
// better heal opponent which most life then the one with the break;
// lowest }
if (!hasTgt) { }
Player opp = opps.max(PlayerPredicates.compareByLife()); if (!hasTgt) {
sa.getTargets().add(opp); // same for opponent lifegain not positive
hasTgt = true; for (Player opp : opps) {
} if (!ComputerUtil.lifegainPositive(opp, source)) {
} sa.getTargets().add(opp);
if (!hasTgt) { hasTgt = true;
return false; break;
} }
} }
return true; }
}
} // still no luck, try to target ally with most life
if (!allies.isEmpty()) {
Player ally = allies.max(PlayerPredicates.compareByLife());
sa.getTargets().add(ally);
hasTgt = true;
}
// better heal opponent which most life then the one with the
// lowest
if (!hasTgt) {
Player opp = opps.max(PlayerPredicates.compareByLife());
sa.getTargets().add(opp);
hasTgt = true;
}
}
if (!hasTgt) {
return false;
}
}
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More