Compare commits

..

2678 Commits

Author SHA1 Message Date
Chris
6d9e785aec [maven-release-plugin] prepare release forge-1.5.13 2014-02-21 14:18:55 +00:00
Chris
e33be38fa6 Preparing the changes.txt file for the next beta build and release. 2014-02-21 13:48:15 +00:00
Sloth
734b6f748e - Fixed Souls of the Faultless. 2014-02-21 13:39:37 +00:00
Sloth
104e6dc306 - Fixed BecomesBlockedAi. 2014-02-21 13:35:34 +00:00
drdev
a336cd7b0e Start input handling 2014-02-20 22:17:39 +00:00
drdev
f62f83bdda Avoid enabling Scissors preemptively 2014-02-20 20:50:14 +00:00
drdev
08ec2cc9d9 Refactor Graphics to support clipping and avoid drawing things outside the bounds of its parent 2014-02-20 20:47:27 +00:00
moomarc
4425029892 - missed a commit 2014-02-20 17:04:39 +00:00
moomarc
3b571cb7ec - Moved Commander to Constructed match setup.
- Variants menu removed
2014-02-20 17:04:13 +00:00
drdev
8edd870602 Refactor out FContainer from FDisplayObject 2014-02-20 16:02:57 +00:00
Chris
f81c3f40b8 Added new card names to changes.txt. 2014-02-20 13:29:10 +00:00
drdev
0cd306d23b Fix support for alpha colors 2014-02-20 07:21:53 +00:00
drdev
cafff3f379 Add logo to home screen 2014-02-20 06:49:15 +00:00
drdev
04d336f674 Support rendering text 2014-02-20 06:31:40 +00:00
swordshine
4f8babf927 - Added Ogre Geargrabber 2014-02-20 05:33:38 +00:00
drdev
c2bbd84204 Make proper background texture be rendered 2014-02-20 04:33:10 +00:00
drdev
f87f3489d1 Make rendering work for mobile game 2014-02-20 04:29:10 +00:00
drdev
77329e8fe5 Refactor FSkin for mobile game 2014-02-20 02:09:35 +00:00
drdev
c947da28c2 More mobile game refactoring 2014-02-19 23:37:59 +00:00
drdev
de51f43301 Refactor mobile Forge game 2014-02-19 22:41:15 +00:00
Sloth
bec8706fcf - Added the hard quest opponent Surtr 3. 2014-02-19 14:43:22 +00:00
Chris
7b6026e17d Added new card names to changes.txt. 2014-02-19 12:38:37 +00:00
asepetci
1324b0754b Updated draft rankings 2014-02-19 06:49:21 +00:00
swordshine
7da1d97769 - Added Leonin Arbiter 2014-02-19 02:28:14 +00:00
Maxmtg
1b5ba15321 ai mana costs will be adjusted by game state 2014-02-18 23:31:33 +00:00
Maxmtg
b299711be7 This restores mana payment prompts (lost yesterday) 2014-02-18 20:28:03 +00:00
Agetian
164490ffc9 - Get rid of the TextUtil import, use the splitting convention used everywhere else in CardFactoryUtil. 2014-02-18 14:11:02 +00:00
Agetian
eadba12739 - Comment fixes. 2014-02-18 13:57:40 +00:00
Agetian
e52e154774 - Added a way to count specific colors of mana spent on X (Count$XColorPaid followed by a list of colors in one-letter abbreviation format e.g. Count$XColorPaid WUG).
- Added Soul Burn.
2014-02-18 13:55:15 +00:00
Chris
878c037554 Added new card names to changes.txt. 2014-02-18 12:52:43 +00:00
moomarc
03cdddce6e - Fixed a bug with team allocation for variant matches 2014-02-18 12:30:13 +00:00
swordshine
f23f17be4f - Fixed Chorus of the Conclave 2014-02-18 11:51:19 +00:00
Maxmtg
fa423233ab only remove javadoc and whitespace 2014-02-18 09:11:23 +00:00
Maxmtg
c9c15d0157 remove custom maps, use guava multimaps 2014-02-18 08:33:40 +00:00
drdev
1b2e23b309 Start working on mobile home screen 2014-02-18 06:29:48 +00:00
drdev
acaa61319f Support loading skins without crashing 2014-02-18 04:18:41 +00:00
drdev
c476415bfc Create symbolic links to allow asset files to be shared amongst gui projects 2014-02-18 03:51:03 +00:00
Sol
5e3786fe1c - Fix for Players not being assigned to any team causing issues from Quest mode 2014-02-18 03:04:40 +00:00
swordshine
07915701c3 - Added Jeweled Amulet 2014-02-18 02:45:28 +00:00
Maxmtg
b69228e28b I believe this would break ai's mana payment 2014-02-18 01:12:37 +00:00
Maxmtg
42fcd856d0 remove getManaPaid() from SpellAbility because there is already a method getPayingMana()
Remove input/InputPayManaExecuteCommands.java, using Common input instead
2014-02-18 00:35:45 +00:00
Maxmtg
22be9fffb6 moved some specific code to ai 2014-02-17 22:19:45 +00:00
moomarc
204f65faac - Archenemy moved to Constructed match setup
- Basic team setup implemented
2014-02-17 16:12:21 +00:00
moomarc
7e2b336ecc - Fixed previous commit 2014-02-17 16:10:14 +00:00
moomarc
df86422e1b - Attempt to implement a basic team victory 2014-02-17 15:53:41 +00:00
moomarc
89c2130ff6 - Attempt to stop AI players in multiplayer games from always only attacking the human until dead. They will now gang up on an opponent with less than 8 life but otherwise attack a random opponent. Feel free to improve the ai. 2014-02-17 15:47:04 +00:00
Sloth
9d90ca99cb - Improved DestroyAllAi. 2014-02-17 15:18:03 +00:00
Chris
f8224a0f7e Added new card names to changes.txt. 2014-02-17 14:58:15 +00:00
moomarc
eef2e004b3 - Improved AI for Idle Thoughts 2014-02-17 14:39:39 +00:00
moomarc
4fda7abaff - Removed unneeded import 2014-02-17 12:01:30 +00:00
asepetci
72aefc9152 Read rankings from cache instead of reading file every time. 2014-02-17 11:03:45 +00:00
Maxmtg
2feac87574 add all triggered abilities to stack when priority is gained, and after devmode cheat 'add to play' 2014-02-17 08:18:48 +00:00
drdev
adcceb74ee Fix so Unique Cards Only option saved between sessions 2014-02-17 07:53:35 +00:00
drdev
9d0db8580e Support saving filters being shown/hidden between sessions 2014-02-17 07:35:47 +00:00
drdev
7106098c91 Support saving column widths and positions between sessions 2014-02-17 07:23:20 +00:00
drdev
51acbeeab2 Fix typo 2014-02-17 07:07:54 +00:00
drdev
77f7031f44 Support saving many item manager options between sessions 2014-02-17 07:02:21 +00:00
drdev
447f07ef7f Refactor how ItemView configuration is setup 2014-02-17 05:08:05 +00:00
swordshine
83a1b4f35e - Prevent NPE when the spell is countered. 2014-02-17 04:33:58 +00:00
swordshine
8cbab3a5a7 - Updated some scripts 2014-02-17 04:16:10 +00:00
Sol
8fdadd61d0 - Added Perplexing Chimera (Exchange portion of ControlSpell) 2014-02-17 03:31:38 +00:00
Sol
1c8b589b1f - Improve description of Poultice Silver 2014-02-17 03:02:39 +00:00
Maxmtg
3d360d216b kill duplicate 2014-02-16 23:21:35 +00:00
Maxmtg
2194592016 This should track the mana paid for each spell 2014-02-16 23:18:21 +00:00
Sloth
7bccaa769a - Fixed playSpellAbilityForFree not being mandatory for the AI. 2014-02-16 20:25:09 +00:00
Sol
1982f73e3d - Added an Effect to gain Control of Spells.
- Added Commandeer
2014-02-16 17:15:44 +00:00
Sloth
4525633ad8 - Added the medium quest opponent Nekusar the Mindrazer 2. 2014-02-16 14:46:55 +00:00
Agetian
6c75215113 - Restore the operation of "remove" and "remove all" buttons in DualListBox so that it's possible to remove added elements one by one or all at once without having to add all the elements first. 2014-02-16 09:08:00 +00:00
Agetian
d841ed5858 - Fix loading skins that have cropped sprite_icons.png with no physical data for missing elements (e.g. the new targeting arrows). Will now return a fully transparent color instead of bailing out with a crash. 2014-02-16 04:12:48 +00:00
drdev
255d508437 Update tooltip for Ranking column 2014-02-15 20:15:31 +00:00
drdev
ee2da8d8cd Remove deprecated Preferences pane from Deck Editor
Reorganize Deck Editor default layout
2014-02-15 20:01:30 +00:00
drdev
c4e4bc1871 Add Unique Cards Only checkbox to options panel on Constructed Catalog 2014-02-15 19:54:27 +00:00
drdev
45ce34ff0b Support showing/hiding columns via checkboxes in options panel
Truncate ranking after 4 decimal places
2014-02-15 19:33:50 +00:00
drdev
f7256824bb Make arrow buttons account for insets 2014-02-15 18:09:27 +00:00
drdev
06f33d21ee Support horizontal scroll button for option panel 2014-02-15 18:00:22 +00:00
drdev
60ae9a110b Prevent both view buttons being toggled off 2014-02-15 17:29:13 +00:00
Agetian
dbabf696ce - Better place for a comment. 2014-02-15 17:12:17 +00:00
Agetian
bcac77bd0d - Non-combat and combat targeting arrows are now themed (use CLR_NORMAL_TARGETING_ARROW and CLR_EFFECT_TARGETING_ARROW).
- Default theme is updated to include a demonstration of where and how to specify the targeting arrow colors.
- Themes which do not have the targeting arrow colors specified will use the default red color with 60% opacity for the combat arrow and CLR_ACTIVE with 60% opacity (as before) for the non-combat arrow.
- Combat targeting arrow opacity is now defined by the theme, not hard-coded.
2014-02-15 17:08:30 +00:00
Agetian
030f6c3939 - Improved the visual appearance of targeting arrows.
- Targeting overlay now displays arrows for creatures attacking planeswalkers.
- Combat arrows (blocking creatures, attacking planeswalkers) are now displayed in red while non-combat arrows are displayed in the more neutral theme-based color.
- Optimized the display of targeting arrows in "always on" mode (should be somewhat faster when many arrows are displayed at once).
- Some refactoring and minor fixes in targeting overlay code.
2014-02-15 15:23:14 +00:00
moomarc
9fbf9903ef - fixed My Undead Horde Awakens 2014-02-15 12:26:30 +00:00
swordshine
0b67f989b9 - Converted keyword "If damage would be dealt to CARDNAME, prevent that damage. Remove a +1/+1 counter from CARDNAME." to script 2014-02-15 04:27:17 +00:00
Agetian
68e8b47838 - Fix the BasicLand definition for Born of the Gods booster packs (uses Theros lands now). 2014-02-14 15:37:18 +00:00
asepetci
52965b233f updated rankings.txt 2014-02-14 15:07:14 +00:00
swordshine
a239710a6b - Update Draft Rankings 2014-02-14 09:09:42 +00:00
drdev
4f86ee6025 Remove bin folders from SVN 2014-02-13 22:40:07 +00:00
Agetian
102468d2e3 - Fix a typo. 2014-02-13 16:53:18 +00:00
Sloth
ee8f8def8e - Prevent NPE in lifeInSeriousDanger. 2014-02-13 15:52:07 +00:00
Chris
4bc8a89544 Added new card names to changes.txt. 2014-02-13 14:59:38 +00:00
swordshine
e66a776f9f - Updated scripts for Sphere of the Suns 2014-02-13 08:38:09 +00:00
drdev
0961590f20 Remove unneeded UIManager call 2014-02-13 06:50:37 +00:00
drdev
8bf4345ac3 Work towards being apply to load skins for Android app 2014-02-13 06:48:53 +00:00
swordshine
e7f0776098 - Fixed a typo 2014-02-13 05:17:48 +00:00
swordshine
35d8ea0949 - Added Daxos of Meletis and Psychic Intrusion (only updated mana color conversion for human players) 2014-02-13 05:13:14 +00:00
drdev
59979056b9 Add start of mobile Forge gui 2014-02-13 04:54:38 +00:00
Sol
dbbb3741d7 - Removing awkward Skeleton Shard cost descriptions 2014-02-13 03:08:02 +00:00
Sloth
7ace65b8c9 - Added SVar:RemAIDeck:True to Peregrine Mask. 2014-02-12 21:29:09 +00:00
Sloth
dcccae1f7c - Fixed AI using Idle Thoughts. 2014-02-12 21:28:28 +00:00
Maxmtg
fa3a140470 fix crashes in deck import when 'use latest edition' is unchecked. 2014-02-12 18:23:42 +00:00
Sloth
d9b657acf4 - Fixed AI suspending Aeon Chronicler for 0. 2014-02-12 15:28:35 +00:00
moomarc
b9046d8326 - Implemented alternate token art parameter in TokenEffect
- updated Eldrazi spawn token generators to reflect correct brood lineage according to https://www.wizards.com/magic/magazine/Article.aspx?x=mtg/daily/arcana/472
- updated token urls with THS tokens
- updated Theros scripts with token image params
2014-02-12 10:12:27 +00:00
swordshine
5b70f38a9c - Fixed TokenTapped 2014-02-12 10:05:21 +00:00
drdev
f94fcfc997 Lock resize cursors when resizing dock panes 2014-02-12 06:50:16 +00:00
Sloth
73e835df10 - Fixed Demonic Tutor and friends revealing the fetched card. 2014-02-12 06:30:39 +00:00
drdev
3dc4594173 Fix so I-Beam cursor displayed when over text fields 2014-02-12 06:29:49 +00:00
drdev
1606bfa2c5 Show resize cursor when hover border between columns and while resizing column 2014-02-12 06:17:17 +00:00
drdev
b39e0f4052 Allow manual resizing of fixed width columns 2014-02-12 05:40:26 +00:00
drdev
f61ac6b361 Column tweaks 2014-02-12 04:41:00 +00:00
drdev
b447b4ab6b Avoid showing unnecessary Folder column in certain DeckManagers 2014-02-12 04:25:20 +00:00
drdev
968a035672 Support marking decks favorites similar to cards 2014-02-12 04:03:49 +00:00
drdev
d976bed702 Support restoring the current deck in the Constructed Deck Editor after closing and reopening Forge 2014-02-11 19:26:29 +00:00
Maxmtg
720a44b4fd change zone ai won't crash with oob 2014-02-11 09:37:55 +00:00
swordshine
fb2287b8c2 - Fixed RearrangeTopOfLibraryEffect 2014-02-11 05:03:26 +00:00
Maxmtg
6468dcc712 bugfix: for cases when no foil is present 2014-02-11 04:56:01 +00:00
Maxmtg
c5923c31e9 DGM booster generation fixes 2014-02-11 04:50:12 +00:00
Sol
7ab28b5226 - Improving two card details text 2014-02-11 04:48:41 +00:00
swordshine
f862a01a78 - Fixed ThisTurnEntered and LastTurnEnded so controller change effects will not count 2014-02-11 04:41:38 +00:00
swordshine
875d019b1c - Fixed 2 cards 2014-02-11 03:58:24 +00:00
Sol
a4271de319 - Remove a phased out token from the battlefield, instead of exiling it, so Attached permanents get permanently phased out 2014-02-11 02:12:13 +00:00
Sol
4d00ab3ecf - Fix crash related to the legendary rule 2014-02-11 01:15:16 +00:00
Sloth
bebe569d10 - Fixed Steamflogger Boss. 2014-02-10 20:03:30 +00:00
moomarc
1c147936d7 - Added links to the Theros booster pack images
- Removed some old Planechase and Vanguard variant stuff
2014-02-10 15:06:13 +00:00
Chris
7e9220d414 Added new card names to changes.txt. 2014-02-10 14:28:24 +00:00
Agetian
01867e5352 - Made FoilAlwaysInCommonSlot the default choice (since all current sets use this option anyway).
- Refactored the edition definitions, now slots newer than TSP do not include the FoilAlwaysInCommonSlot line, while the older sets with foils have a FoilAlwaysInCommonSlot=False line.
- Some definition fixes related to foil generation.
2014-02-10 10:18:56 +00:00
Agetian
09b638075c - Another MMA foil update. 2014-02-10 08:44:52 +00:00
Agetian
79a61f2e12 - Fix for the previous fix. 2014-02-10 08:38:40 +00:00
Agetian
ed39723ab6 - Fix Modern Masters definition to work correctly with foils. 2014-02-10 08:38:03 +00:00
Agetian
b22ce18572 - Fix imports. 2014-02-10 08:34:09 +00:00
Agetian
9b55d3c36b - Update the Modern Masters definition. 2014-02-10 08:33:36 +00:00
Agetian
02bb5942b3 - Get rid of a debug println. 2014-02-10 08:33:24 +00:00
Agetian
a72655fc79 - Read the foil chance in booster value from edition files as a US locale double. 2014-02-10 08:32:49 +00:00
Agetian
30fda0e7a8 - Simplify foil chance calculations, use double to represent chance. 2014-02-10 08:17:34 +00:00
Maxmtg
9957c12ff8 split Command interface - separate class for ui, and another one for game. 2014-02-10 08:11:32 +00:00
Maxmtg
bfee22e968 disbanded 'constant' class from game 2014-02-10 08:04:02 +00:00
Agetian
485aa06339 - Revert the previous representation of foil chances in booster (doesn't work the way I thought it would).
- Changed the default chance of a foil in booster to 0.2143 (21.43%) which seems to match the official 1 foil per 70 cards more closely.
2014-02-10 07:38:13 +00:00
Agetian
bd5471cb30 - A comment is not necessary here (moved elsewhere). 2014-02-10 06:56:40 +00:00
Agetian
39c83b4870 - A more intuitive way to represent foil chance in booster in edition files (1:6 instead of 1667, 1:1 instead of 10000).
- Updated Modern Masters definition file to use the new format.
2014-02-10 06:56:00 +00:00
swordshine
d32858b4c4 - Added False Dawn 2014-02-10 05:21:14 +00:00
Sol
de0348592b - Add an extra Restriction Matrix to handle Celestial Dawn for Mana Conversion
- Added Celestial Dawn
2014-02-10 03:22:19 +00:00
Maxmtg
804a8801e5 Moved ai to dedicated module, fix api dictionary 2014-02-09 21:12:26 +00:00
Maxmtg
dd34a3aa9b remove AI class from ApiType 2014-02-09 21:01:34 +00:00
Maxmtg
0f37de9627 removed another ai hook 2014-02-09 20:25:47 +00:00
Maxmtg
14a0e6e3ac by adding a bit ugly method to player controller, I've removed last isHuman check and the last reference from api executor to AI 2014-02-09 20:22:15 +00:00
Maxmtg
2f2d24eb3e move some ai-classes to right package 2014-02-09 20:15:58 +00:00
Maxmtg
06484f81f6 Separate AI decision making from SpellAbility classes 2014-02-09 20:05:05 +00:00
drdev
b3f019420f Fix so current quest deck remembered correctly 2014-02-09 19:47:33 +00:00
Agetian
2487f7ee93 - Modern Masters now correctly has a foil in every booster pack. 2014-02-09 19:38:54 +00:00
drdev
e9c4f0913d Fix so quest decks can be deleted properly 2014-02-09 19:18:57 +00:00
drdev
81c5912add Remove unused imports 2014-02-09 19:07:10 +00:00
Hellfish
767524fb57 *More overzealous rename refactoring done away with 2014-02-09 17:44:00 +00:00
Hellfish
40f7d164bf *Fix overzealous rename refactoring 2014-02-09 17:38:39 +00:00
Hellfish
1ff23dcc56 *Apparently the reintroduced TriggerReplacementBase was never added. It is now! 2014-02-09 17:33:14 +00:00
Agetian
a4e9aa6243 - Added the star icon to old-style (pre-8E) foils for authenticity. 2014-02-09 16:41:19 +00:00
Hellfish
50c40311a7 *Reintroduced TriggerReplacementBase as a specialized subclass of CardTraitBase 2014-02-09 16:03:27 +00:00
Chris
d331c87ab5 Added new card names to changes.txt. 2014-02-09 15:24:50 +00:00
Hellfish
c4e1552b8b *StaticAbility shares some functionality with Trigger and ReplacementEffect, extend their base instead of duplicating code 2014-02-09 14:42:39 +00:00
Hellfish
94783575cc *Prettied up TriggerReplacementBase 2014-02-09 14:14:08 +00:00
Hellfish
a54a0ba985 *Moved common functionality of Trigger and ReplacementEffect into base class. 2014-02-09 13:56:04 +00:00
swordshine
e9da0ec519 - Fixed Fact or Fiction 2014-02-09 11:19:54 +00:00
swordshine
8a9a13c5d7 - "enters the battlefield tapped with counters" now is a single replacement effect. 2014-02-09 10:43:27 +00:00
swordshine
85d967e5a5 - Fixed Eureka so these cards actually start with the activator when resolving 2014-02-09 10:39:48 +00:00
swordshine
453bf76d63 - Added Fearsome Temper 2014-02-09 10:36:23 +00:00
swordshine
72bb290215 - Added Contamination 2014-02-09 10:30:21 +00:00
swordshine
6230b6f28b - Converted "CARDNAME enters the battlefield tapped unless you control a" keyword to an ETBtapped replacement effect. 2014-02-09 10:28:35 +00:00
swordshine
0f5228259a - Fixed Mistbind Clique 2014-02-09 10:18:46 +00:00
Sloth
4b5bd3d619 - Added Vortex Elemental. 2014-02-09 09:10:38 +00:00
Maxmtg
1ad1714388 fix hybrids payment {2/C} 2014-02-09 07:22:08 +00:00
drdev
f10fd27869 Fix so view has focus when opening FDeckViewer 2014-02-09 06:05:56 +00:00
drdev
1f883c993b Fix so switching group by options retains all groups being collapsed
Add option to group by rarity
2014-02-09 05:30:14 +00:00
drdev
319b470acd Prevent crash when attempting to being build Draft deck immediately after ending drafting process and saving the draft 2014-02-09 05:10:51 +00:00
Agetian
e09ec6c165 - Better variable name. 2014-02-09 04:45:40 +00:00
Agetian
1fa0a2c81c - Fixed a bug in booster generator that caused the foil from a wrong print sheet to be generated for sets that did not have a foil always in common slot. 2014-02-09 04:41:54 +00:00
Sol
a8e54c5052 - Added Warping Wurm by squee1968 2014-02-09 03:39:53 +00:00
drdev
f17fa767c0 Sort draft pack by rarity then by color 2014-02-09 02:57:00 +00:00
Sol
c8eed79a2a - Initial checkin for Additive Mana Conversion Static Ability (Restrictive Conversions currently not 100% correct)
- Added Mycosynth Lattice
- Fixed Sunglasses of Urza
2014-02-09 02:37:09 +00:00
Sol
537f95c79d - Fixed infinite loop caused by Max's trigger fixes when no triggers are on the stack 2014-02-09 02:10:19 +00:00
Maxmtg
c923a7d976 fix comile erre 2014-02-08 22:49:56 +00:00
Maxmtg
b0939aeed8 bug fix 2014-02-08 21:10:23 +00:00
Maxmtg
a7d359fa10 loop until nothing added to stack, AP is turn owner 2014-02-08 20:52:02 +00:00
drdev
862d8ed857 Fix so foils display properly in CardManager Image Views 2014-02-08 20:41:07 +00:00
drdev
83cebd7e82 Fix label 2014-02-08 20:26:30 +00:00
Maxmtg
1eeab52cdf all player will add their triggers to stack when ANY player has priority 2014-02-08 20:24:35 +00:00
Agetian
686807b0d2 - A closer approximation of foil chance in booster (16.67% for 1:6 ratio instead of just 16%). 2014-02-08 20:03:01 +00:00
Agetian
f0464395ab - A little correction. 2014-02-08 19:55:25 +00:00
Agetian
87de44dd04 - Added information about extended foil support to CHANGES.txt. 2014-02-08 19:55:06 +00:00
Agetian
5fe65ad74b - Added a preference to support enabling/disabling foil overlay display in GUI.
- Tied the card panel foil display to the user preference setting.
2014-02-08 19:35:36 +00:00
drdev
58600fbfad Lock input while refreshing images 2014-02-08 19:34:37 +00:00
drdev
c92be334ae Maximize space for viewing pack cards by eliminating Choose Card button and hiding view options by default 2014-02-08 18:51:55 +00:00
drdev
8276b5c51a Support apply foil effect to card images in CardManager Image View 2014-02-08 18:29:04 +00:00
Agetian
d7224d7515 - Fixed a bug that caused the game to crash when generating boosters with no specified code (fixes full cardpool draft).
.
2014-02-08 18:24:04 +00:00
drdev
96ff2dc09e Support hiding filters by default on Draft screen 2014-02-08 18:23:21 +00:00
Agetian
a40da18132 - Added Born of the Gods quest precons. 2014-02-08 17:34:49 +00:00
drdev
8cb889da68 Prevent horizontal line appearing when hiding filters and not showing button panel 2014-02-08 17:28:21 +00:00
drdev
08d2d7f4e8 Prevent Add Player button getting stuck 2014-02-08 17:22:39 +00:00
Maxmtg
e9c273e40f logic optimized 2014-02-08 17:13:35 +00:00
Maxmtg
9500b3d951 acquire booster edition once 2014-02-08 17:07:15 +00:00
drdev
2b4e935373 Align buttons 2014-02-08 17:01:23 +00:00
Agetian
4f9fd821ec - Added foil generation in booster packs (the UI support is currently incomplete, foils will not be distinguished in deck editors or displayed in pile view or booster card list for now; also, there is currently no option to disable the foil visual effect in GUI). 2014-02-08 16:16:23 +00:00
Agetian
b221b7eaab - Added a method to add a color replacement to ManaPool.
- Fixed a mistype in method name.
2014-02-08 16:04:54 +00:00
Chris
4b8ed1df67 Added new card names to changes.txt. 2014-02-08 15:19:30 +00:00
Chris
9399097069 Cleared out the changes.txt file, now ready for new material. 2014-02-08 15:17:27 +00:00
Maxmtg
337f5b7320 clean up code style 2014-02-08 15:01:46 +00:00
Agetian
80c70d321e - Append the foil suffix to the card name instead of to the end of the line (looks better in the deck file and fixes foil card recognition). 2014-02-08 13:16:36 +00:00
Maxmtg
676fee32c6 renamed add/subtract methods, fixed bug with colorless payment 2014-02-08 12:56:54 +00:00
Maxmtg
715ee1f9ab color conversion routine for mana implemented 2014-02-08 12:21:55 +00:00
Maxmtg
c5a791f361 more paths require manapool 2014-02-08 11:33:17 +00:00
Maxmtg
1ec656f2ed ManaCostBeingPaid no longer be built from string, its "pay" methods require reference to manaPool (which is to hold color replacement rules) 2014-02-08 11:26:27 +00:00
Sloth
97b579a68d - Fixed Lightning Volley. 2014-02-08 07:47:31 +00:00
drdev
59f75710dc Add Expand/Collapse all groups button to Image View 2014-02-08 04:47:45 +00:00
drdev
c7e74b2ddc Add combo box as way to change column count in addition to Ctrl+MouseWheel 2014-02-08 02:54:59 +00:00
drdev
6380b4c8fe Change image scaling method to be based on column count rather than fixed size 2014-02-08 02:21:27 +00:00
Sol
3d4f9e7297 - Committing Ertai's Familiar and Shimmering Efreet by squee1968 2014-02-07 17:29:15 +00:00
Chris
bfe48d2611 [maven-release-plugin] prepare for next development iteration 2014-02-07 17:25:48 +00:00
Chris
3c08718b4b [maven-release-plugin] prepare release forge-1.5.12 2014-02-07 17:25:37 +00:00
Chris
2f033f795f Preparing the changes.txt file for the next beta build and release. 2014-02-07 15:10:33 +00:00
drdev
acd2ca2f23 Add GroupBy and PileBy combo boxes to Image View 2014-02-07 07:42:56 +00:00
drdev
de2731c21f Group sealed pool by color by default 2014-02-07 06:36:26 +00:00
drdev
5e0a8a6331 Add view options panel to ItemManager 2014-02-07 06:26:28 +00:00
drdev
b34ca7b951 Support canceling when selecting block or custom draft format when setting up a new draft or sealed deck
Prevent crash if you select a block that has no sets
2014-02-07 05:20:08 +00:00
drdev
1422748447 Prevent double-click failing after a previous double-click on a control opened a different screen 2014-02-07 04:25:31 +00:00
drdev
186f7f70e4 Fix so hover doesn't get stuck after right-clicking card in deck viewer 2014-02-07 04:11:56 +00:00
drdev
4b1eb98e5b Make Image View the default in all decklist ItemManagers 2014-02-07 04:05:45 +00:00
drdev
f7e185be3b Fix flaw with previous fix 2014-02-07 03:55:10 +00:00
Agetian
ea6da0b7dc - Make the zebra skin color on Marble Blue skin easier on the eyes. 2014-02-07 03:51:25 +00:00
drdev
ae23652c0c Avoid odd display when first opening image view 2014-02-07 03:51:17 +00:00
drdev
54b86b4db2 Update CHANGES.txt 2014-02-07 03:32:25 +00:00
drdev
f45779d2c3 Don't enforce 4-of card limit in deck editor if not enforcing deck legality 2014-02-07 03:28:13 +00:00
drdev
480ebbb121 Include information about ItemManager ImageView in CHANGES.txt 2014-02-07 01:00:12 +00:00
Chris
427e8c7059 Added new card names to changes.txt. 2014-02-06 15:01:20 +00:00
RumbleBBU
86cf1b63f4 Fixed quest starting pool color bias selection. 2014-02-06 08:25:41 +00:00
drdev
27ee03494b Improve restored scroll position after adjusting image view zoom 2014-02-06 05:36:46 +00:00
Maxmtg
2f5799f453 routed all checks like 'can pay for this shard with mana of certain color' through either player's manapool, or a method in ManaCostBeingPaid. Will add a color conversion matrix at these 2 points later. This change is a prerequisite to implement cards like Daxos of Meletis 2014-02-06 04:55:30 +00:00
drdev
7c419d875d Fix so image size goes from 50 to 300 2014-02-06 04:52:17 +00:00
Maxmtg
d7d87b5c5d CardDb is now able to report the unique missing cards (it's not a good idea to keep it all in ctor, should refactor later) 2014-02-06 04:00:51 +00:00
Sol
a11e7bbcb8 - Introduce PhaseIn and PhaseOut triggers
- Added Teferi's Imp (as an example of both)
2014-02-06 03:01:21 +00:00
drdev
028e4bc196 Support image view scaling using Ctrl+MouseWheel 2014-02-06 02:28:54 +00:00
Sloth
2252b9908b - Updated mtg-data.txt. 2014-02-05 17:05:21 +00:00
Sloth
02353704ca - Fixed Seize the Soul and friends. 2014-02-05 16:35:14 +00:00
Sloth
2bde0ac099 - Fixed lands with "May be played by your opponent" being playable by the owner. 2014-02-05 16:11:59 +00:00
Chris
2543aabbd4 Added new card names to changes.txt. 2014-02-05 13:25:59 +00:00
Maxmtg
d01c22b5f9 Change zone - only the choose card left inside if condition 2014-02-05 08:15:56 +00:00
swordshine
08896bdb8e - Converted "When CARDNAME is dealt damage, destroy it." keyword to script 2014-02-05 06:28:17 +00:00
swordshine
46d7491c78 - Update scripts 2014-02-05 06:26:05 +00:00
swordshine
9437ea5d90 - Converted Fastbond and Naya to script
- Missing file for Felhide Spiritbinder
2014-02-05 06:23:45 +00:00
swordshine
184943da83 - Fixed some scripts 2014-02-05 06:21:41 +00:00
swordshine
66e53ad1ed - Added Felhide Spiritbinder, Hunter's Prowess, Lightning Volley 2014-02-05 06:17:49 +00:00
Maxmtg
6595893e48 Add BNG cycle of creatues with "inspired - pay {2}{C} to put some tokens onto battlefield"
Fix: Oracle text for Searing Blood
2014-02-05 06:14:52 +00:00
Maxmtg
14ac5860f8 removed from EXT banlist cards whose editions left the format 2014-02-05 05:40:25 +00:00
drdev
4554d94f32 Prevent crash when Control clicking a selected card 2014-02-05 03:26:01 +00:00
drdev
737b2eb0f7 Prevent FMouseAdapters getting screwed up if popup appears between mouse down and mouse up 2014-02-05 02:03:07 +00:00
drdev
cb318eb8b9 Ensure scrolled to top after loading deck in list view 2014-02-05 01:25:52 +00:00
drdev
557035e340 Focus list when header clicked
Improve behavior of removing items in Image View
2014-02-05 01:21:48 +00:00
drdev
86bd570e2c Lock hovered item while context menu or zoomer open 2014-02-05 00:41:58 +00:00
drdev
d406114118 Add middle (and left+right) click zoom support to Image View 2014-02-05 00:15:24 +00:00
drdev
e4dd0faf5e Code cleanup 2014-02-04 23:02:04 +00:00
drdev
83aa60a77c Code cleanup 2014-02-04 22:59:12 +00:00
Sloth
acfe00d6db - Fixed a bug in ChooseSourceAi. 2014-02-04 19:55:45 +00:00
Maxmtg
d4a2ae07df code formatting 2014-02-04 18:13:56 +00:00
Maxmtg
926bdc1d5a Fix wrong filtering in 2 piles effect 2014-02-04 18:13:45 +00:00
drdev
ffee3af0ac Use same border for hovered and selected items 2014-02-04 18:06:33 +00:00
Maxmtg
42e6dffda2 Fixed problem with antes being drawn before players are assigned 2014-02-04 18:02:06 +00:00
drdev
c9db375d7c Fix multiple selection for image view 2014-02-04 17:53:30 +00:00
drdev
06de741370 Prevent auto-selecting card when first opening deck, changing filters, or expanding/collapsing groups 2014-02-04 17:45:23 +00:00
drdev
a2287aff5b Improve appearance of group headers
Allow deselecting cards in image view
2014-02-04 17:25:28 +00:00
drdev
444aad2034 Wrap piles 2014-02-04 16:10:27 +00:00
Chris
0de852043e Added new card names to changes.txt. 2014-02-04 14:49:52 +00:00
Sloth
9df1102514 - Added a space to message. 2014-02-04 14:24:06 +00:00
Sloth
8eaa535d6b - The AI will no longer kill itself with Mogis, God of Slaughter. 2014-02-04 13:59:44 +00:00
Sloth
b5e7de67a2 - The AI will no longer kill itself with Vendetta. 2014-02-04 13:47:55 +00:00
swordshine
f4db8078b8 - Converted Chains of Mephistopheles to script (it's a normal replacement effect, the original hardcoded script is not correct) 2014-02-04 08:13:50 +00:00
swordshine
21db65e9ab - Update state-based actions (704_5r now fire remove counter triggers, Brother Yamazaki now work correctly with Humility) 2014-02-04 08:09:25 +00:00
swordshine
1d0b60427d - Fixed ETBtapped effect
- Converted Transmute to script
2014-02-04 08:06:01 +00:00
swordshine
1760b10940 - Update scripts 2014-02-04 08:03:20 +00:00
swordshine
ce54805981 - Update banlist 2014-02-04 07:54:52 +00:00
swordshine
6df75248d8 - Added Gild and Satyr Firedancer 2014-02-04 07:52:47 +00:00
drdev
21331d4189 Support sorting cards into piles 2014-02-04 06:36:52 +00:00
drdev
c1e72c380b Prevent unnecessary refresh when setting up Image View groupBy/pileBy 2014-02-04 04:35:42 +00:00
drdev
b38346e00c Prevent changing collapsed state of empty group 2014-02-04 04:29:16 +00:00
drdev
9e5cbbb9d1 Prevent prompting to leave draft when exiting after already leaving a draft 2014-02-04 04:24:59 +00:00
drdev
f767ca34ce Fix so programatically selecting an item in collapsed group auto-expands group 2014-02-04 04:07:16 +00:00
drdev
d4bc720dd0 Fix so selection on Image View not lost on resize 2014-02-04 03:56:04 +00:00
drdev
1d1950d3cb Fix so hovering cards in FDeckViewer shows Details/Picture in dialog as expected 2014-02-04 03:50:00 +00:00
drdev
038418ca0d Fix selection after expand/collapse group 2014-02-04 03:29:34 +00:00
drdev
4aa2dd4471 Prevent auto-expanding groups when filters change
Fix how multi-type cards are grouped
2014-02-04 03:04:41 +00:00
drdev
075a01e3ae Fix display glitch 2014-02-04 02:14:55 +00:00
drdev
35ea4c2a50 Add grouping support for Deck editors 2014-02-04 01:56:46 +00:00
Sloth
bc851d0b51 - Fixed spells cast by AF Play effects (like Isochron Scepter) not triggering cast spell triggers (like storm). 2014-02-03 20:08:25 +00:00
Sloth
c11d353540 - Fixed Searing Blood. 2014-02-03 19:59:50 +00:00
Sloth
32e6da4a78 - Fixed Herald of Torment. 2014-02-03 19:40:26 +00:00
Chris
a96f9be77a Added new card names to changes.txt. 2014-02-03 13:52:35 +00:00
Maxmtg
04a38b79ac Old-format decks will load without exceptions 2014-02-03 08:16:44 +00:00
Maxmtg
a1886d51f8 mana effect - will give all colors to choose from if manaAb.expressChoice was empty 2014-02-03 06:41:14 +00:00
Agetian
9cd2bc595d - Fixed ManaEffect with AnyMana crashing, e.g. for Chromatic Star (temporarily fixed it by reverting the relevant part to the previous version, feel free to revert and commit a more optimal fix) 2014-02-03 06:25:51 +00:00
Maxmtg
e592d3bc56 add god-favoured general (script by moomarc) 2014-02-03 05:33:58 +00:00
Sol
1da213b157 - Improved Card Detail description of Buyback and Entwine 2014-02-03 04:43:37 +00:00
Sol
3f349d0eb3 - Improved Card Detail description of Suspend 2014-02-03 04:33:43 +00:00
Sol
84cac99a59 - Improved Card Detail description of Echo 2014-02-03 04:11:12 +00:00
Sol
de1b198f4c - Adding Mana symbols to Unnerving Assault 2014-02-03 03:22:25 +00:00
Sol
5954e7053c - Adding Mana symbol to Vigor Mortis 2014-02-03 02:46:02 +00:00
Maxmtg
1b4b9ad32b change zone... still didn't dare to change it =) 2014-02-02 18:11:32 +00:00
Agetian
394e96d051 - A little clarification in CHANGES.txt. 2014-02-02 15:42:16 +00:00
Chris
2c6151996e Added a fluff piece to the changes.txt file. 2014-02-02 15:22:36 +00:00
Maxmtg
5c195f3cea moved forge.net.protocol to .net project 2014-02-02 13:31:55 +00:00
Maxmtg
1af6d101e8 fix mana atoms (bug emerged with 24602) 2014-02-02 12:32:46 +00:00
Maxmtg
b6b220496b do not add target folder into svn 2014-02-02 11:17:49 +00:00
Maxmtg
ff92c4cf31 add forge.net project (to hold network protocol, FServer and similar things) 2014-02-02 11:16:39 +00:00
Maxmtg
12ed04a7cc non-supported cards will have a default image 2014-02-02 10:56:44 +00:00
Maxmtg
6575000c28 better loading of unsupported cards 2014-02-02 10:26:22 +00:00
Agetian
43f4e45827 - Some more error message mistype / spelling fixes. 2014-02-02 10:22:49 +00:00
Agetian
4b6e58ddf0 - A little mistype fix. 2014-02-02 10:19:59 +00:00
Maxmtg
0abdf7a3b5 CardDb - unsupported cards detection
CardPool - fix bug in decks save
2014-02-02 10:03:41 +00:00
Maxmtg
53e0a2ad37 createUnsuportedCard -> cardDb 2014-02-02 09:39:22 +00:00
Agetian
7e22527164 - Send unsupported card names to the console so that the user is aware of which cards failed to load when the decks were parsed. 2014-02-02 09:22:49 +00:00
Maxmtg
7e6928d700 Split Deck- storage and serializer parts into their own classes 2014-02-02 09:11:48 +00:00
Agetian
4fa37ac4b9 - Delete the unwanted file that got pulled into the last commit. 2014-02-02 08:51:34 +00:00
Maxmtg
bbe5e3c556 small tweaks 2014-02-02 08:51:03 +00:00
Agetian
4c7f16baf4 - Most random card pools (quest shop, quest starting pool, sealed deck, booster draft) will now generate only one kind of Zendikar lands (full art or standard art) at a time instead of mixed art of both kinds. 2014-02-02 08:44:46 +00:00
Maxmtg
cec3d26493 re-arranged deck serialization code to make it more "symmetric": the what is serialized somewhere tends to be de-serialized in the same class 2014-02-02 08:39:12 +00:00
Maxmtg
86edf9fd66 nothing important 2014-02-02 01:56:26 +00:00
Maxmtg
0adcfe55aa made deck color filters null-tolerant 2014-02-02 01:53:45 +00:00
Maxmtg
5a22e6a15d generated decks will not attempt to calculate their color 2014-02-02 01:49:00 +00:00
Maxmtg
4d833b6f38 getColor method moved to DeckProxy 2014-02-02 01:42:54 +00:00
Maxmtg
d05ecdf840 propper filtering of deck proxies 2014-02-02 01:40:32 +00:00
Maxmtg
1f66ec432d shifted MagicColors 1 bit to the right 2014-02-02 01:08:53 +00:00
drdev
2fcc5943fa Refactor Sections to Groups and create enum for grouping items 2014-02-02 00:53:03 +00:00
Maxmtg
08e909ca00 remove another isHuman check 2014-02-02 00:28:24 +00:00
Maxmtg
4c48e3e089 Reveal empty list won't crash the game 2014-02-02 00:05:41 +00:00
Maxmtg
d9d32f7508 fix hangs when playing spell from devmode that triggers something
remove unneeded code
2014-02-01 23:53:08 +00:00
Maxmtg
691c52fff9 Forge will no longer crash when there are decks with unsupported cards. These cards will appear in decks and games player starts as a dummy without cost, types, abilities, but with explanation in card's text 2014-02-01 21:43:42 +00:00
drdev
c3d7a9f075 Refactor so Image View shares sort order of List View 2014-02-01 21:16:12 +00:00
Maxmtg
da3b2c1dc1 "take priority" changed: now on priority SBA is checked, triggers are ordered, then player is asked for spell they want to play, then spell is played. Until player refuses to choose a spell, that means "I pass" 2014-02-01 20:43:20 +00:00
drdev
2ea69dde48 Show picture and details on hover for image view 2014-02-01 19:31:55 +00:00
Chris
7c66069ee3 Cleared out the changes.txt file, now ready for new material. 2014-02-01 14:36:16 +00:00
Sol
fd9ee6f4e1 - Fix Clean remembered line for Arbiter of the Ideal 2014-02-01 13:59:53 +00:00
Sloth
6314550377 - AI can now target with Tyrant of Discord. 2014-02-01 09:22:15 +00:00
Agetian
738fc7f52d - Fixed the Suspended Sentence precon description. 2014-02-01 07:02:12 +00:00
drdev
4c129ffee4 Add hover effect to ImageView 2014-02-01 04:47:14 +00:00
Sol
c1750989b7 - Fixing Asphyxiate cost 2014-02-01 01:53:21 +00:00
drdev
2b685348ab Add image view to item managers 2014-02-01 01:16:43 +00:00
Maxmtg
070fd7695a phase won't end if players are allowed to have priority and there are sim-stack entries to be added 2014-01-31 23:59:54 +00:00
Maxmtg
7cf1a52405 orderSimultaneousStackEntries only when player gains priority 2014-01-31 21:51:50 +00:00
Maxmtg
096f41172d remove some playertype checks 2014-01-31 19:57:59 +00:00
Chris
59665029d2 [maven-release-plugin] prepare for next development iteration 2014-01-31 16:18:08 +00:00
Chris
3249e844f9 [maven-release-plugin] prepare release forge-1.5.11 2014-01-31 16:17:57 +00:00
Chris
187c90c7df Preparing the changes.txt file for the next beta build and release. 2014-01-31 15:40:38 +00:00
Chris
d779034da8 Added new card names to changes.txt. 2014-01-31 15:36:38 +00:00
moomarc
429d8dbf30 - Added Searing Blood 2014-01-31 07:46:08 +00:00
Maxmtg
c5d35c9596 added a method: magic color form char 2014-01-31 05:55:30 +00:00
Maxmtg
23265d2623 moved a method from ai to effect class (because the code being moved does not take decisions) 2014-01-31 05:12:45 +00:00
drdev
4810518d74 Code cleanup 2014-01-31 00:22:14 +00:00
Sloth
1e389fdef8 - Added oracle texts to BNG cards. 2014-01-30 21:57:20 +00:00
Sloth
f31550732e - Added Forsaken Drifters, Servant of Tymaret and Warchanter of Mogis. 2014-01-30 21:45:54 +00:00
drdev
29876aeb28 Code cleanup 2014-01-30 20:50:23 +00:00
Sloth
a90c34aac4 - Updated some SVars. 2014-01-30 19:34:17 +00:00
moomarc
78456d6333 - Removed duplicate script for Sapphire Drake 2014-01-30 17:47:11 +00:00
moomarc
44edc80abc - BNG: Added Pain Seer 2014-01-30 17:45:55 +00:00
Sloth
3f6e6f54a1 - Updated some SVars. 2014-01-30 16:38:46 +00:00
moomarc
1e576cfde6 - BNG: Added Black Oak of Odunos 2014-01-30 16:15:59 +00:00
moomarc
07c4ab24e7 - BNG: Added Retraction Helix 2014-01-30 16:02:34 +00:00
Sloth
915a6a42c2 - Added Sphinx's Disciple and Stratus Walk. 2014-01-30 13:27:34 +00:00
Chris
5216b35688 Added new card names to changes.txt. 2014-01-30 13:25:04 +00:00
Maxmtg
3c53eec798 fix CostRemoveCounter for X counters 2014-01-30 07:10:13 +00:00
moomarc
db080d8b10 - BTG: Added Acolyte's Reward 2014-01-30 06:46:58 +00:00
drdev
04d129b8d8 Cleanup look of vanguard and planar deck panels 2014-01-30 02:30:11 +00:00
drdev
059e7f7920 Fix so first player's avatar has focus when Forge first opened 2014-01-30 01:58:49 +00:00
drdev
3bbfe87868 Code cleanup 2014-01-30 01:48:57 +00:00
Sloth
8cdc76427e - Added Kraken of the Straits and Meletis Astronomer. 2014-01-29 19:58:07 +00:00
Maxmtg
11976bf964 fix year of BNG set 2014-01-29 17:20:38 +00:00
Maxmtg
a5f4f78018 won't throw when not expected 2014-01-29 17:02:32 +00:00
Maxmtg
0621f2e242 refactor isAPermanentType to be less ugly 2014-01-29 16:59:06 +00:00
Chris
5538480862 minor edit. 2014-01-29 16:10:42 +00:00
Sloth
07f126cb52 - Updated Cloudstone Curio. 2014-01-29 15:47:45 +00:00
Sloth
a5263b1375 - Updated scripts of Blind Fury, Bloodletter Quill, Goblin Warchief and Opal Palace. 2014-01-29 15:11:25 +00:00
Sloth
6a52bdf10d - Merged BNG branch into trunk. 2014-01-29 15:00:25 +00:00
moomarc
ad38be34b7 - updated Changes.txt to reflect the latest state of the new constructed match screen 2014-01-29 11:05:33 +00:00
moomarc
a7785b0d1b - disabled checkboxes for Archenemy and Commander in constructed match setup.
- disabling a variant now updates the deck selector as appropriate
2014-01-29 10:57:16 +00:00
moomarc
26743d0ca0 - uploaded missing precon images and updated download URLs 2014-01-29 10:19:13 +00:00
moomarc
fccc8f129f - formalised/fixed some precon image names/links
- added Sol's python script for checking for missing precon images
2014-01-29 08:43:57 +00:00
Maxmtg
c1a11dd89a natural ordering of cards now includes art idnex 2014-01-29 06:49:48 +00:00
Agetian
0730e2d774 - Changed typeContains so that it can correctly identify Planes apart from Planeswalkers (fixes the card zoomer incorrectly showing planeswalkers rotated 90 degrees). 2014-01-29 06:46:51 +00:00
drdev
a8bf257391 Refactor Spell Shop prompts to only appear once no matter how many items are being bought/sold together 2014-01-28 22:51:56 +00:00
Agetian
86f6a9e3f1 - Attempt to fix image cache visualizing wrong card pictures for cards with multiple art. 2014-01-28 16:24:38 +00:00
Chris
41f3b22237 Added new card names to changes.txt. 2014-01-28 15:17:03 +00:00
Maxmtg
7ad5bfb581 rollback bug 2014-01-28 09:31:20 +00:00
Maxmtg
75e5c8d0ca fix precon loading issues 2014-01-28 07:32:10 +00:00
Maxmtg
1b9437ad32 fix artindex 2014-01-28 07:11:41 +00:00
Maxmtg
d5bde7570a correct picture 2014-01-28 06:58:48 +00:00
Maxmtg
a0d13578f3 artIndex inside the very cards is 1-based as well (no more adjustments of +/-1 needed) 2014-01-28 06:56:42 +00:00
Maxmtg
ed48547f36 very minor tweaks 2014-01-28 03:50:28 +00:00
Agetian
73b97fe151 - Fix for the previous commit. 2014-01-27 18:18:43 +00:00
Agetian
f21e1c3f8a - Make generated quest card pool indices 1-based. 2014-01-27 18:15:54 +00:00
Maxmtg
aa230be806 quest save indices 2014-01-27 17:53:35 +00:00
Maxmtg
26bb8e5009 art index bugfix for decks being loaded 2014-01-27 17:37:54 +00:00
Agetian
5322d5a9c2 - Reapply the previous NPE fix. 2014-01-27 16:06:36 +00:00
Agetian
0f5eb7d5f8 - An alternate NPE fix as suggested by Max. 2014-01-27 15:47:35 +00:00
Agetian
9379f7283c - Added descriptions to new precon decks.
- Fixed a NPE when trying to show the description of a precon in spell shop that lacks the description line in its metadata.
2014-01-27 15:45:24 +00:00
Maxmtg
29025cce61 step 2 on hidden origins 2014-01-27 08:48:28 +00:00
Maxmtg
7fd201d5e3 merge ChangeZoneEffect.java hidden part to make it invariant of player types (step 1) 2014-01-27 07:52:13 +00:00
moomarc
1f947d5781 - selecting a vanguard avatar updates the button label 2014-01-27 07:52:01 +00:00
Agetian
8dea53bcda - A better fix for the custom booster draft NPEing 2014-01-27 07:06:20 +00:00
Agetian
f5d43d0986 - Fix imports. 2014-01-27 06:43:13 +00:00
Agetian
cf67fb456e - Temporarily revert the previous fix, will recommit a better one soon. 2014-01-27 06:42:35 +00:00
moomarc
308814bdca - removed a few unnecessary lines 2014-01-27 05:51:06 +00:00
Agetian
149a9bd7b2 - Fix a crash when choosing Custom Draft.
- Note that this fix gets rid of a seemingly useless/buggy piece of code in GuiChoose, I can't remember why if there was a good reason for it in the first place. Please report if I broke something else by removing it.
2014-01-27 05:39:42 +00:00
Agetian
e16998beb5 - Fixed the ability to choose to use the deck-default Vanguard avatar. 2014-01-27 05:22:19 +00:00
Maxmtg
a9e12e38ef update something on ai/human playertype change 2014-01-26 22:26:25 +00:00
Maxmtg
9c7d25f49d arranged components inside a panel 2014-01-26 22:02:57 +00:00
Maxmtg
a52a1f5820 Threw in an idea for playerPanels in constructed screen 2014-01-26 18:54:10 +00:00
Agetian
e0c3244667 - Compilation fix 2014-01-26 17:17:33 +00:00
moomarc
32bf18781a - Vanguard matches now launched via constructed match screen 2014-01-26 15:51:43 +00:00
Chris
5bd88d468e Added new card names to changes.txt. 2014-01-26 14:59:07 +00:00
Agetian
e758e07658 - Fix a typo in image name. 2014-01-26 05:06:44 +00:00
swordshine
eb0ac0dde9 - BNG: Added Whims of the Fates 2014-01-26 04:56:56 +00:00
Maxmtg
c81a4d50dd auto set assignment - report to CHANGES.txt 2014-01-25 23:20:23 +00:00
Maxmtg
ae28b04615 A couple of intro decks from GPT times 2014-01-25 23:01:40 +00:00
Maxmtg
471623cd10 3 Starters from M11 added 2014-01-25 22:51:45 +00:00
Maxmtg
4378bfdbea release art indices on M14-precons 2014-01-25 22:42:12 +00:00
Maxmtg
8ff6cbd98e better art randomization for loaded decks 2014-01-25 22:36:15 +00:00
drdev
f4ffe22d61 Refactor ItemManager to switching between different views 2014-01-25 22:33:50 +00:00
Maxmtg
4573e30e73 refactor and remove some dependencies 2014-01-25 22:31:32 +00:00
Maxmtg
832b40e235 Add theros intro decks 2014-01-25 22:10:41 +00:00
Maxmtg
aec0bad678 more problems with art indices solved 2014-01-25 21:49:53 +00:00
Maxmtg
aeedde578d exclude schemes, planes etc from xitax-conversion 2014-01-25 21:40:29 +00:00
Maxmtg
dfebb08e1e fix deck loading problem (wrong editions were shown) 2014-01-25 21:30:56 +00:00
Maxmtg
dbf77d8548 M14 intro decks added to precons 2014-01-25 20:04:16 +00:00
Maxmtg
003c252d8a added option to use only core and expansion sets in deck importer, fixed a related bug 2014-01-25 19:54:32 +00:00
Maxmtg
1986df1961 Xitax's deck convertor integrated into Forge - applies on load to decks where no card has explicitly defined set. 2014-01-25 19:10:26 +00:00
Maxmtg
11c205f0c9 fix bugs in minimal set calculation.
added that column to itemmanager
2014-01-25 16:29:56 +00:00
drdev
7d2cc8fac5 Use arrow button scroller for Variant panel 2014-01-25 16:07:04 +00:00
drdev
863e7733e6 Prevent unmaximizing unless mouse moved 5 pixels on title bar 2014-01-25 16:04:37 +00:00
Maxmtg
e657a1b07d Moved ColumnDef to separate file 2014-01-25 15:58:16 +00:00
Maxmtg
2f29cf7755 edition cached in deckproxy 2014-01-25 15:50:56 +00:00
Maxmtg
6d34dbc286 refactored cardDb, added getEdition method to DeckProxy 2014-01-25 15:43:48 +00:00
Sloth
28f7f7baaf - Fixed Myr Reservoir. 2014-01-25 15:26:58 +00:00
Chris
f3b78b1aa3 Added a fluff piece to the changes.txt file.
Added new card names to changes.txt.
2014-01-25 14:31:19 +00:00
moomarc
ce82a93afe - Fixed starting a constructed match with no variants applied 2014-01-25 12:21:28 +00:00
Maxmtg
fe9a35b706 fix crash 2014-01-25 11:19:01 +00:00
moomarc
febba22f45 - Updated booster and precon lists 2014-01-25 09:56:33 +00:00
Maxmtg
2981668172 PreconDecks' folder property will be filled with their set (temporary) 2014-01-25 09:56:14 +00:00
Maxmtg
ed7369c98f store variants in an enumset 2014-01-25 09:45:47 +00:00
Maxmtg
b6409b11c7 fix NPE when you close any dialog prompting for random name options 2014-01-25 09:37:24 +00:00
Sloth
98c51e45b4 - Fixed Night Dealings. 2014-01-25 07:39:51 +00:00
drdev
9e6b82d10a Add "Folder" filter for DeckManagers 2014-01-25 06:39:48 +00:00
drdev
ee49314502 Fix so planar and scheme decks are saved in the right place 2014-01-25 06:14:17 +00:00
Agetian
29e73367a6 - Planes and phenomena will now be shown rotated 90 degrees in card zoomer mode.
- Updated the AI hints for Onakke Catacomb.
2014-01-25 06:04:50 +00:00
drdev
80aa655164 Add "Folder" column to DeckManagers 2014-01-25 05:50:12 +00:00
drdev
f8d7d060b9 Fix so saving a deck multiple time works
Fix so adding/removing decks in All Decks pane updates deck choosers on home screen
Fix so saving a deck in Deck Editor updates All Decks pane right away
Fix so typing in deck title field marks deck as having changes to save
2014-01-25 05:12:09 +00:00
drdev
b198e8a40e Code cleanup 2014-01-25 02:48:36 +00:00
drdev
a3fb6de6b3 Fix so decks inside subfolders support being deleted 2014-01-25 01:02:51 +00:00
moomarc
5252cca5c5 - small update to changes.txt 2014-01-24 20:33:23 +00:00
moomarc
4cab5da076 - Planechase variant now launched from Constructed match setup screen.
- moved random deck checkboxes into deck panel. Only show them when a random deck type is selected. They are still global settings however.
- when player 1 name is changed in preferences, switching back to constructed setup screen will reflect the change
2014-01-24 19:06:31 +00:00
Sloth
2cdf7030f8 - Made some CantBeBlocked keywords HIDDEN. 2014-01-24 15:37:54 +00:00
Chris
e30f8d0fb6 Preparing the changes.txt file for the next beta build and release. 2014-01-24 14:12:10 +00:00
Chris
22fe17048d Added new card names to changes.txt. 2014-01-24 14:07:24 +00:00
swordshine
3d70e2e9f6 - BNG: Added Gorgon's Head, Siren Song Lyre, and Thunderous Might 2014-01-24 07:20:41 +00:00
Maxmtg
f35dc1f30d apply random foil option for quest games 2014-01-24 06:58:52 +00:00
Maxmtg
34b36a8ef2 optimized formats calculation for decks,
added some comments to deckmanagers
2014-01-24 06:55:38 +00:00
Maxmtg
7c105ca398 Deckmanager : "Edit deck" button (icon) now opens decks from nested folders correctly 2014-01-24 06:43:22 +00:00
Maxmtg
2ec4873552 match itself no longer creates a separate thread, it is now caller's responsibility. Some callers (such as tests or simulations) may run their match in the same thread. 2014-01-24 06:32:45 +00:00
Maxmtg
aa4f3de30c done with Dependencies
added a class to hold all game rules (type, gamesToWinMatch, manaburn, etc)
2014-01-24 06:19:09 +00:00
Maxmtg
e4fb939e91 free blocks option moved to PlayerControllerHuman 2014-01-24 05:44:23 +00:00
Maxmtg
68acaf77c5 cut Dependencies on log entry type and AI profiles 2014-01-24 05:21:21 +00:00
Maxmtg
308039739f Random foiling moved to per-player settings, is set up before starting a match 2014-01-24 04:35:22 +00:00
swordshine
7687637783 - BNG: Added Astral Cornucopia, Fated Return, and Pillar of War 2014-01-24 01:06:57 +00:00
drdev
7b799eb531 Restore borders that were accidentally removed from scroll panes 2014-01-23 23:15:08 +00:00
drdev
9c37156906 Remove unnecessary setting of scroll pane border to null 2014-01-23 22:30:54 +00:00
Chris
32d6eb3ed5 Added new card names to changes.txt. 2014-01-23 14:36:05 +00:00
swordshine
faa2cef0bb - BNG: Added Shrike Harpy and Scourge of Skola Vale 2014-01-23 12:52:26 +00:00
Maxmtg
7e999ea4b3 Cut the dependence on images. Game module will store image keys, unaware of real image locations 2014-01-23 09:11:51 +00:00
swordshine
69c569c82b - added missing subtype arcane to Cranial Extraction 2014-01-23 08:34:49 +00:00
Agetian
bcc5f2c526 - A little fix for the fix. 2014-01-23 07:29:06 +00:00
Agetian
35bff6a89b - Use CLR_ZEBRA for the game log window for consistency with the deck editor tables and to make some themes (e.g. Firebloom) easier on the eyes. 2014-01-23 06:24:21 +00:00
Agetian
a26d45793e - Fix the formation of card choice window title for DigEffect. 2014-01-22 19:00:20 +00:00
Chris
d6577d1d72 Added new card names to changes.txt. 2014-01-22 15:00:22 +00:00
swordshine
e8aeec7f87 - BNG: Added Nessian Demolok and Oracle of Bones 2014-01-22 10:58:32 +00:00
Maxmtg
0b75a0ee8e fix duplication of copies brought by "bftb" 2014-01-22 07:31:52 +00:00
Maxmtg
4163c56502 back from the brink now brings back the cards (but created 2 copies for unknown reason) 2014-01-22 07:14:53 +00:00
drdev
37d45ffbeb Use base ZEBRA and tweak skins that would otherwise have unreadable text 2014-01-22 06:57:09 +00:00
drdev
f03a1397f1 Use dark ZEBRA instead of THEME2 as base background color for Item Manager tables 2014-01-22 06:44:32 +00:00
drdev
410b23974e Make alternating row colors more distinct and remove horizontal grid lines 2014-01-22 05:35:10 +00:00
Maxmtg
3366843ca1 added a method to reload a proxied object from storage (call this method after the object has been changed in deck editor) 2014-01-22 05:26:26 +00:00
Maxmtg
9bcc6cbc64 save/delete methods implemented on DeckProxy 2014-01-22 05:06:56 +00:00
drdev
9636e3d2e4 Prevent player column stretching up over variants panel when players added 2014-01-22 04:59:54 +00:00
drdev
140e44a3bd Add title to avatar selector
Return focus to proper player after avatar selector closed
2014-01-22 04:50:24 +00:00
Maxmtg
9f76a78b0a visbility fix 2014-01-22 04:36:36 +00:00
Maxmtg
dd6e8d0884 rename field 2014-01-22 04:35:08 +00:00
drdev
66b1a795c1 Make Quest Duels and Quest Challenges screens use FScrollPanel with arrow buttons
Prevent focusing arrow buttons
2014-01-22 04:29:19 +00:00
Maxmtg
8b8b9f0cf5 add methods to search for nested folders and create them 2014-01-22 04:22:31 +00:00
drdev
d5647c9e98 Move arrow buttons support to FScrollPane to allow better separation of FScrollPane and FScrollPanel 2014-01-22 04:09:38 +00:00
Maxmtg
5d1d77f4df generalize DeckProxy to manage Limited mode decks efficiently 2014-01-22 03:52:06 +00:00
drdev
7a193c0618 Fix so adding cards to sideboard in Quest Deck Editor properly removes them from inventory 2014-01-22 03:43:07 +00:00
drdev
1709ed07c7 Prevent weird diamond shaped scrollbar thumbs 2014-01-22 03:34:58 +00:00
drdev
23586a027e Make FLabel colors static 2014-01-22 03:21:45 +00:00
drdev
97c74ec2b1 Make scroll arrow buttons semi-transparent and more like FLabels 2014-01-22 03:14:23 +00:00
drdev
961461aa19 Dim scrollbars unless mouse over them 2014-01-22 02:28:52 +00:00
drdev
713a9ecb3b Prevent long deck name causing horizontal scroll arrow to appear 2014-01-22 00:29:36 +00:00
drdev
8a46d38914 Fix so previously played deck restored properly again 2014-01-22 00:12:44 +00:00
drdev
423033d2ac Code cleanup 2014-01-21 23:55:17 +00:00
Maxmtg
d01925107f fix label on 'Choose deck' button 2014-01-21 18:54:02 +00:00
Maxmtg
47a719147c load decks from subfolders
display formats, sb/main sizes
2014-01-21 18:38:17 +00:00
Agetian
556bb2caa7 - Mention the new random art option in CHANGES.txt. 2014-01-21 14:39:34 +00:00
Agetian
ec3813fb13 - Fix getCbRandomArtInPools returning the wrong checkbox.
- Some more renaming.
2014-01-21 14:31:53 +00:00
Agetian
926152940e - Better name for the random art in generated card pools option. 2014-01-21 14:23:46 +00:00
Agetian
7cf186952e - Added an option "Randomize card art in generated card pools" (enabled by default). When enabled, generates cards with different art in generated limited mode pools (sealed, booster draft, quest). When disabled, generates a certain predefined amount (30 for draft/sealed, 10 for quest shop) of lands, all with the same art. However, the art index will be randomized at every generation, thus still leaving some room for art randomization.
- Tweaked case of letters in some option names for consistency.
2014-01-21 14:17:01 +00:00
Chris
b2ccb5c689 Added new card names to changes.txt. 2014-01-21 13:51:48 +00:00
Agetian
3da6e688e4 - Fixed the "remove this card if not playing for ante" inversion causing the ante cards to be removed when playing for ante and not removed when not playing for ante. 2014-01-21 11:04:08 +00:00
Maxmtg
567ffe84cd DeckManagers use DeckProxies 2014-01-21 09:32:36 +00:00
swordshine
d97495ed41 - BNG: Added Heroes' Podium 2014-01-21 08:47:10 +00:00
Maxmtg
ceab176d9f improved the codestyle for Dependencies left from gui 2014-01-21 06:59:28 +00:00
Maxmtg
050887c53c fix compile problems 2014-01-21 06:34:03 +00:00
Maxmtg
2bc61a9e68 the great move 2014-01-21 06:27:36 +00:00
Maxmtg
6b1d9356f3 move authorization to see downface of cards to classes that won't move to different module 2014-01-21 06:20:08 +00:00
Maxmtg
1dbc34fe62 move command and constant, set up bridges for dependencies 2014-01-21 06:16:10 +00:00
Maxmtg
d93b041397 moved 2 files to ensure history is saved 2014-01-21 06:11:18 +00:00
Maxmtg
ecd14e1de4 Revert module separation to make another commit that would keep files' history 2014-01-21 06:05:34 +00:00
Agetian
923bddc90d - Generate 10 lands per art in sealed deck and booster draft modes if there is more than one art available (to avoid spamming the sideboarding window with many hundreds of basic lands) or just generate 30 basic lands of each type if there is only one art for the basic land in set (should still be reasonably enough for any sane limited play). 2014-01-21 05:58:56 +00:00
Maxmtg
97297f291f Game now compiles and runs! 2014-01-20 19:01:29 +00:00
Maxmtg
84850c76a9 restore compilation ability - 1 method left 2014-01-20 18:57:00 +00:00
Maxmtg
ca8c8d35d9 commited a workaround for preferences dependency 2014-01-20 18:44:43 +00:00
Maxmtg
18e96a1028 module separation in progress. Project cannot be compiled. Will restore in under an hour 2014-01-20 18:33:47 +00:00
Maxmtg
f27b3e469a last call to gui eliminated in game code, will try to extract module next 2014-01-20 18:01:12 +00:00
Maxmtg
7b47938062 remove method from AbilityUtils that adds reference from Game to Gui 2014-01-20 17:47:00 +00:00
swordshine
d9f3bb8bdd - BNG: Added Spirit of the Labyrinth 2014-01-20 14:32:16 +00:00
swordshine
8919532b84 - Convert Dredge to a replacement effect 2014-01-20 14:13:13 +00:00
Chris
481ac50726 Added new card names to changes.txt. 2014-01-20 12:45:54 +00:00
Maxmtg
731b3c8c36 DeckProxy (work in progress) 2014-01-20 09:13:36 +00:00
Maxmtg
8393bc02e4 remove ItemPoolView class, instead a read-only pool will be an instance of same class, but backed with an unmodifiable map 2014-01-20 09:13:13 +00:00
Maxmtg
85e5b0ff13 removed list or items from CardPool that had to be kept in sync with the main map, since no other class but ItemManagerModel used it (I wanted to do that for too long already!) 2014-01-20 08:49:52 +00:00
Maxmtg
3b8d779b27 reverted another change to core (the ordering of colors for random generation is implemented in a simpler way) 2014-01-20 08:20:28 +00:00
Maxmtg
89dcef5b67 moved convoke cards selection code from ManaCostBeingPaid to dedicated input for human player 2014-01-20 07:32:12 +00:00
moomarc
6ced3d38a4 - ComboBoxWrapper will retain whether its combobox is enabled or not with a skin change 2014-01-20 07:05:47 +00:00
swordshine
7fc97562b2 - BNG: Added Fanati of Xenagos and Flame-Wreathed Phoenix 2014-01-20 06:59:51 +00:00
moomarc
80372f40b5 - Added manual avatar selection to constructed match setup. 2014-01-20 06:23:57 +00:00
Sol
d9a5a27391 - Fixed Antes Panel from updating too early, and not updating when cards are added to the Ante zone. 2014-01-20 04:29:30 +00:00
Sol
8c73aa93c8 - Adding missed bazaar index file from last checking for new Quest item 2014-01-20 04:16:09 +00:00
drdev
59997f5cf2 Skin scrollbars 2014-01-20 03:03:43 +00:00
drdev
3ab7db6061 Change ItemTable border to match filter separators 2014-01-20 00:26:58 +00:00
drdev
3d2ba5a182 Tweak alt row color so it contrast better with inactive selection color 2014-01-20 00:18:56 +00:00
drdev
d7cb5165ef Skin ItemManagers 2014-01-20 00:14:40 +00:00
Agetian
3a89700d95 - The AI will add snow-covered lands to maindeck when building limited decks (sealed, draft). 2014-01-19 19:04:22 +00:00
drdev
f0ce722aa4 Don't retain selection when clicking to change column sort
Support showing color deck options in proper order with no column header
2014-01-19 18:15:02 +00:00
Chris
95709401a3 Added new card names to changes.txt. 2014-01-19 14:54:50 +00:00
swordshine
06f030ab73 - BNG: Added Champion of Stray Souls 2014-01-19 11:36:37 +00:00
Sloth
bd0dc07c43 - Fixed the keyword "At the beginning of your upkeep, sacrifice CARDNAME unless you pay ..." broken by the brackets {}. 2014-01-19 11:16:54 +00:00
swordshine
0d354f679c - BNG: Added Arbiter of the Ideal, Everflame Eidolon, and Mindreaver 2014-01-19 04:10:20 +00:00
Sol
7ed37ec5f8 - Created a new Bazaar item "Cash Stakes" to allow cards lost in ante to be available in the card shop immediately after the match 2014-01-19 03:15:24 +00:00
swordshine
fa40deaa11 - BNG: Added Tromokratis 2014-01-19 01:33:24 +00:00
Sloth
117d25b686 - Prevention for infinite loops in payManaCost. 2014-01-18 20:41:30 +00:00
Sloth
7491eb0076 - Fixed AI using Sphere of the Suns. 2014-01-18 20:28:50 +00:00
drdev
1acb9f8ab1 Fix so restored deck in view when application first opened
Prevent selection going out of view when table height changes
2014-01-18 20:04:23 +00:00
drdev
f87b262194 Fix so selection retained when changing sort
Fix so previously played deck restored on Constructed screen
2014-01-18 19:26:01 +00:00
Sloth
6c82cabe3a - Updated the quest deck Abe Sapien 3. 2014-01-18 18:25:34 +00:00
drdev
7e7969988a Rework player zone message building for ability effects 2014-01-18 17:06:18 +00:00
moomarc
4b51c24b66 - removed a console println 2014-01-18 15:28:40 +00:00
Sol
5e0ad38ffc - Cards lost to Ante in Quest mode will now appear in the Spell Shop for repurchase 2014-01-18 14:53:31 +00:00
Chris
56557101f3 Added new card names to changes.txt. 2014-01-18 13:36:12 +00:00
drdev
1d0cf99e48 Add View Deck button to make FDeckViewer more discoverable 2014-01-18 07:14:30 +00:00
drdev
2f5b534de6 Make quantity column narrower in FDeckViewer 2014-01-18 06:57:34 +00:00
drdev
00ae4d4824 Add card detail and picture panels to FDeckViewer 2014-01-18 06:42:25 +00:00
swordshine
af2a87a207 - BNG: Added Archetype of Courage, Archetype of Endurance, and Archetype of Imagination 2014-01-18 06:15:53 +00:00
drdev
72fe5ad996 Add FDeckViewer to display deck list when double-clicking
Improve support for opening modal dialog on top of another modal dialog
2014-01-18 05:50:11 +00:00
drdev
d00da28a3c Prevent cursor changing to Move every time you click a tab 2014-01-18 04:06:42 +00:00
drdev
81363bc537 Revert my revert of Max's revert (now that I see what I did wrong, sorry Max) 2014-01-18 03:49:45 +00:00
drdev
1e98cd4939 Fix so combo box constraints retained when switching skins 2014-01-18 03:23:36 +00:00
drdev
814a8976fb Show tooltip for truncated caption 2014-01-18 02:55:09 +00:00
drdev
86cea92ca1 Truncate item manager caption if not enough room for it 2014-01-18 02:53:32 +00:00
drdev
f9178dcff2 Fix so Constructed layout is at least usable at minimum resolution
Fix so Archenemy combo box supports skin changes
2014-01-18 02:33:36 +00:00
swordshine
fcc4b92efe - Fix last commit 2014-01-18 02:19:54 +00:00
swordshine
44f79a5db2 - BNG: Added Brimaz, King of Oreskos and Oreskos Sun Guide 2014-01-18 02:15:56 +00:00
drdev
034f235196 Show color and other columns for quest opponent decks 2014-01-18 01:26:59 +00:00
swordshine
a1e16543b2 - BNG: Added Temple of Enlightenment, Temple of Malice, and Temple of Plenty 2014-01-18 01:24:23 +00:00
drdev
8578e2a538 Fix case of random button 2014-01-18 01:21:37 +00:00
drdev
092d3dac40 Fix padding around new random name button 2014-01-18 01:19:15 +00:00
drdev
e9ddf8400b Revert Max's revert 2014-01-18 00:31:45 +00:00
Maxmtg
22b78255bb Storages must not be changed for UI needs 2014-01-17 17:23:50 +00:00
Agetian
2985b48432 - Updated Alara block precons with authentic basic land art [by Diogenes]. 2014-01-17 16:45:46 +00:00
Agetian
b249028b8e - Code simplification related to effective art index calculation. Deprecated art index 0 will now be treated as random art (please update your decks if they use art index 0). 2014-01-17 16:39:21 +00:00
Agetian
4cb2077e1a - The art indexes in .dck files now start at 1, not at 0, and thus match the image file name conventions (e.g. index 1 for a Forest matches Forest1.full.jpg). Note that if you had decks created with different art in mind prior to this beta, the art in your decks will be slightly off (index 0 will be treated as index 1 for the sake of limited backwards compatibility). Please update your decks if necessary. 2014-01-17 15:37:19 +00:00
Agetian
88b6223820 - Fix imports. 2014-01-17 15:05:11 +00:00
Agetian
0b4ef6b1e3 - Experimental: generate 50 basic lands for each art type in limited modes (Sealed Deck and Booster Draft, in particular). While this is not theoretically "unlimited" as the rules for limited modes provide, it guarantees at least 150-200 basic lands of each type for every set with three or four pictures, and at least 50 basic lands of each type even if there's only one art; this probably should be more than necessary for any sane limited play needs). Feedback welcome. 2014-01-17 15:04:12 +00:00
Agetian
8921ce16e6 - Basic NPE prevention in showCard, please review (not sure if there's a better way to prevent this NPE that would be more desirable). 2014-01-17 14:37:35 +00:00
Chris
f4606da30f Added new card names to changes.txt. 2014-01-17 14:14:50 +00:00
moomarc
41a5727a93 * Added a button to game setup to fetch random names 2014-01-17 13:14:14 +00:00
Maxmtg
19cec963d2 moved a gui reference out from costs 2014-01-17 08:32:39 +00:00
swordshine
156bc84c73 - Update more scripts 2014-01-17 07:52:22 +00:00
swordshine
eb98eaf5af - Update more scripts 2014-01-17 07:30:27 +00:00
swordshine
fb6d86af8a - Update scripts 2014-01-17 07:18:54 +00:00
drdev
39aa6bfda1 Add a little more padding between columns 2014-01-17 05:58:46 +00:00
drdev
ba59dfb01c Allow clicking anywhere inside player panel to select it
Distinguish selected player better using semi-transparent overlay for other panels
2014-01-17 05:55:30 +00:00
drdev
dd044c30e6 Prevent scroll arrow button sticking around when switching to other pages on home screen 2014-01-17 05:01:46 +00:00
drdev
f1fcc2c26b Increase gap below variants panel 2014-01-17 04:52:58 +00:00
drdev
c0e9d82ff6 Improve Constructed screen layout 2014-01-17 04:40:35 +00:00
Agetian
89598ab48c - Fixed apostrophes in Vintage format banned list definition. 2014-01-17 02:53:32 +00:00
drdev
586de9321a Remove copy decklist message 2014-01-17 01:16:05 +00:00
drdev
ae7244a10f Fix so toggle button filters appear properly when removed then re-added 2014-01-17 00:54:21 +00:00
drdev
d95fc7352f Cleanup unused references 2014-01-16 23:42:27 +00:00
moomarc
e4b7c92c6a - New constructed match setup improvements:
* Skinned player close button
2014-01-16 18:47:58 +00:00
drdev
a52a88e0cb Fix so DeckManagers list decks in nested folders
Update FDeckChooser to use DeckManager
2014-01-16 15:57:43 +00:00
moomarc
2ec3c08e72 - New constructed match setup improvements:
* changing the avatar in match setup or avatar preferences will now update each other to reflect the change to the preferences
2014-01-16 15:41:54 +00:00
Chris
b57e0a9848 Added new card names to changes.txt. 2014-01-16 13:49:09 +00:00
Chris
eabcc78815 Added new card names to changes.txt. 2014-01-16 13:46:49 +00:00
moomarc
1b7a2730f0 - New constructed match setup improvements:
* When a player is removed, the closest active player gains focus.
2014-01-16 11:17:18 +00:00
moomarc
caac7dc2ed - New player name generator applied 2014-01-16 10:17:38 +00:00
Maxmtg
d8eb3a5c7a DeckChooser will notify of their selection change 2014-01-16 06:40:22 +00:00
Agetian
5973d0d944 - Added some more information about the WIP functionality targeted for the upcoming beta. 2014-01-16 05:16:36 +00:00
Agetian
a512a7ab33 - Added some information about the WIP functionality targeted for the upcoming beta. 2014-01-16 05:11:10 +00:00
Agetian
e44cada50e - [Fixed] only save the information about the card art index to .dck file if the card has more than one possible art in set. 2014-01-16 04:45:17 +00:00
Sloth
49e734b66d - Added code support for Ragemonger. 2014-01-15 22:31:18 +00:00
Agetian
5c4efbcd04 - Minor code style tweak. 2014-01-15 16:01:57 +00:00
Agetian
f131aafa44 - Optimized the generation of basic lands with random art for the quest mode card pool and quest shop.
- Better name for a function to split a value into a predefined number of random groups.
2014-01-15 15:59:57 +00:00
Chris
f4263e943d Added new card names to changes.txt. 2014-01-15 14:07:07 +00:00
moomarc
45cd72f7dc - New constructed match setup improvements:
* avatar randomization now ensures duplicates aren't chosen
  * player names and avatars are now used for the match
2014-01-15 12:07:45 +00:00
Agetian
634289407b - Optimization of adding cards with random art index in CardPool. 2014-01-15 09:34:03 +00:00
moomarc
01ff93375a - New constructed match setup fixes:
* starting a game without preferences set no longer causes issues with avatars or name.
  * avatars are assigned avatars randomly (except for p1 and p2 who uses their preferences)
  * right clicking an avatar assigns a new random avatar
2014-01-15 08:10:55 +00:00
Agetian
c64ef5deed - Fix a crash in the multiple art processing code related to cards not assigned to any edition. 2014-01-15 07:50:27 +00:00
drdev
a5a0c510b5 More code cleanup 2014-01-15 05:48:53 +00:00
drdev
d7f8231717 More code cleanup 2014-01-15 05:20:00 +00:00
drdev
f0f7cfc839 Move remaining storage classes out of GUI 2014-01-15 05:14:05 +00:00
drdev
03870821c5 Code cleanup 2014-01-15 05:07:59 +00:00
Agetian
726084bc06 - Removed a check for variant card art index (all variant cards currently have only one art). 2014-01-15 05:02:50 +00:00
swordshine
cbbc341472 - Remove creature subtypes when gods are not creatures 2014-01-15 04:54:36 +00:00
drdev
664b707330 Fix so multicolor decks shown if only multicolor button checked 2014-01-15 02:21:26 +00:00
drdev
a47bba59d4 Fix so right-click updates table selection even if no context menu is shown 2014-01-15 02:10:36 +00:00
drdev
e84544afe4 Fix deck format default sort 2014-01-15 01:49:07 +00:00
drdev
cd505adc3d Fix padding around deck list buttons 2014-01-15 01:46:11 +00:00
drdev
5b1dd964ae Fix warnings 2014-01-15 01:33:07 +00:00
Sol
86ef4d0820 - Adding support for Gain Ownership cards, utilizing some of the same methods Ante is already using
- Added Darkpact
2014-01-15 01:06:14 +00:00
moomarc
966e593617 - Initial implementation of new constructed game setup 2014-01-14 16:04:10 +00:00
Agetian
eb4a355dc7 - Only write out the card art index to the deck if the art count for the card is greater than 1. 2014-01-14 15:42:47 +00:00
Agetian
d65b645a64 - Added a function to CardDb to get the number of different art available for a given card in a given set (to be used soon). 2014-01-14 15:27:33 +00:00
Chris
f899c6e58c Added new card names to changes.txt. 2014-01-14 14:30:40 +00:00
Agetian
a554599fd7 - Updated the Duel Decks: HvM edition file to correctly recognize 8 Mountains with different art (thanks Diogenes!) 2014-01-14 13:53:47 +00:00
swordshine
6046860e15 - BNG: Added Hero of Iroas
- Tweak Bestow
2014-01-14 12:15:24 +00:00
Maxmtg
1dda7d72a9 restore fields( in case GameFormat is serialized) 2014-01-14 07:35:07 +00:00
Maxmtg
3e2f3ab1c5 small bug 2014-01-14 07:22:39 +00:00
Maxmtg
286d7d043c Restored functionality : deckeditor shows all valid formats for decks in its list. 2014-01-14 07:21:49 +00:00
Maxmtg
563c089b9d now GameFormat supports "Restricted" cards (ex: power 9 in Vintage) 2014-01-14 07:13:48 +00:00
Agetian
ca103655bd - Optimize splitCardName by getting rid of continuous regular expression parsing. 2014-01-14 06:56:06 +00:00
Agetian
666fb6b35c - Cards with no art index specified will now have random art in every match (e.g. the line "20 Forest|7ED" provides twenty 7th Edition Forest cards each with randomized art).
- Some code reogranization related to card art randomization.
2014-01-14 06:02:06 +00:00
swordshine
d234975cde - BNG: Added Nessian Wilds Ravager and Pharagax Giant 2014-01-14 04:40:40 +00:00
drdev
13a6310ac0 Increase height of table rows slightly 2014-01-14 03:55:54 +00:00
drdev
62bfb66e11 Tweak spell shop columns 2014-01-14 03:39:48 +00:00
drdev
b776929fe8 Ignore sideboard when determining deck color (mostly for sake of limited) 2014-01-14 03:16:05 +00:00
drdev
273b7286cf Tweak cost column width 2014-01-14 02:27:45 +00:00
drdev
5bd450e0c9 Fix deck sort logic 2014-01-14 02:19:51 +00:00
drdev
777dff3c4b Adjust column widths 2014-01-14 02:06:15 +00:00
drdev
e3ddb00eee Left align table headers and ColorSetRenderer
Fix default sort for Format column
2014-01-14 01:54:45 +00:00
Maxmtg
870ffe3437 better color sorting, colorless decks icon is properly centered 2014-01-13 19:30:07 +00:00
Maxmtg
b8ea837204 align colors on center 2014-01-13 19:21:39 +00:00
Maxmtg
4a611fcf58 fix problems with deck format detection 2014-01-13 19:01:46 +00:00
Maxmtg
91be7b5a32 ColorSetRenderer for deck list 2014-01-13 18:46:40 +00:00
Maxmtg
42cf0def0f rollback changes to Deck, ManaCost, StorageBase, CardPool 2014-01-13 18:21:29 +00:00
Agetian
10fd05478d - Sealed Deck and Booster Draft limited modes now correctly handle and support cards with different art. 2014-01-13 14:46:12 +00:00
Chris
9fbc7a17c3 Added new card names to changes.txt. 2014-01-13 14:29:06 +00:00
Agetian
3cd105bac0 - Restore the functionality to show all cards from all sets and with different card art in the Constructed deck editor. This is temporarily the default and only behavior because it's the only way to properly support cards from multiple sets and with multiple card art indexes at the same time (given the note below).
- NOTE: The "Show unique cards only" functionality of the Constructed deck editor is currently broken (it was commented out by someone, possibly to be reworked later?)
2014-01-13 10:48:38 +00:00
swordshine
559c96412a - BNG: Added Ephara, God of the Polis 2014-01-13 09:56:21 +00:00
Agetian
ee5187c7e7 - Snow-covered lands have only one art (at least thus far). 2014-01-13 09:47:12 +00:00
Agetian
0cb7573494 - Generate basic land cards with random art in quest mode starting pool and quest mode card shop. 2014-01-13 09:46:35 +00:00
Agetian
0f514be363 - Added a simple Python tool to batch-scan a number of deck files for compatibility with Forge AI (use -h to get help). Right now it requires an unpacked 'cardsfolder' and a 'decks' folder in the same folder as the tool in order to operate.
- The -p option will only list decks that are playable by the AI, the -u option will only list decks that are unplayable by the AI (along with the lists of unplayable cards in each deck), the -d option will physically remove all deck files that are not compatible with the AI.
2014-01-13 09:10:34 +00:00
Agetian
ca07a8103a - Use the single-line 'for' style similar to other code places.
- Fix unused imports.
2014-01-13 08:55:47 +00:00
Agetian
4837bb5740 - Support distinction between cards with different card art indexes in the deck editors (the card index is now saved to .dck file and loaded from there if available).
- Remove a Singleton call from Match that was necessary to randomize card art (will be unsupported later and will not be necessary anyway once the true support is fully implemented).
- (WIP) Automatically randomize card art for cards that do not have the art index specified; for now only works for basic lands that were generated randomly (e.g. in random Constructed decks). Limited mode (sealed decks, draft decks, quest mode generated pool, quest mode spell shop) support for generating cards with different card art will hopefully follow soon.
2014-01-13 08:54:04 +00:00
drdev
fc7cc33111 Allow filtering and sorting decks based on name, color, and format
Refactor table column logic to be easier to implement new columns
Provide set sizes for certain columns so other columns can auto-size nicer
2014-01-13 08:35:47 +00:00
Maxmtg
ce341302a3 renamed createIngamePlayer method to minimize confusion 2014-01-13 06:23:32 +00:00
Maxmtg
4cb27c40ac (doubtful) replace xcantbe0 flag with restriction 2014-01-13 06:20:58 +00:00
swordshine
84e3fc1be9 - Fixed ChangeZoneAllEffect (r23967 broke many cards. Sloth, please review this fix) 2014-01-13 02:41:33 +00:00
Maxmtg
2522450930 Kill all gui 2014-01-12 18:32:49 +00:00
Maxmtg
e3e761ddc0 -1 gui call 2014-01-12 18:27:37 +00:00
Maxmtg
c95ee53137 remove gui calls from RepeatEachEffect.java 2014-01-12 18:22:26 +00:00
Maxmtg
7f9ef96178 wipe gui calls from effects 2014-01-12 18:16:03 +00:00
Maxmtg
95cbb2870c event phased had to be game event, not purely UI one 2014-01-12 17:34:40 +00:00
Maxmtg
17891a62fa remove global object reference from ZoneType 2014-01-12 17:20:45 +00:00
Maxmtg
f1ff893962 Human payment for costs now uses visitor pattern... as a result the concrete Cost classes no longer depend on UI 2014-01-12 16:15:41 +00:00
Chris
ce6d092b16 Cleared out the changes.txt file, now ready for new material. 2014-01-12 15:31:45 +00:00
Maxmtg
f6577217ff remove 'put' method from ItemPool , use 'add' instead 2014-01-12 12:22:12 +00:00
Maxmtg
283af44c48 removed deckbox 2014-01-12 12:16:51 +00:00
Maxmtg
1ea839f5d4 All costs except mana - AI uses decision, HumanPlay generates decision 2014-01-12 11:53:58 +00:00
drdev
4a3e07bbae Fix titles on dig effect prompts
Make reveal dialogs have more consistent titles
2014-01-11 18:26:43 +00:00
Chris
9ba8866b19 The year is now 2014. 2014-01-11 16:10:22 +00:00
Chris
5dbe15e7e0 [maven-release-plugin] prepare for next development iteration 2014-01-11 15:11:47 +00:00
Chris
cbf38aed9c [maven-release-plugin] prepare release forge-1.5.10 2014-01-11 15:11:36 +00:00
Chris
a8eb34d201 Added a fluff piece to the changes.txt file.
Preparing the changes.txt file for the next beta build and release.
2014-01-11 14:54:09 +00:00
drdev
496b12913d Prevent random crash that sometimes occurs when moving mouse over deck tables 2014-01-11 04:15:04 +00:00
drdev
be47a4bad3 Fix crash when clicking a card that can't be played 2014-01-11 01:56:14 +00:00
Sol
9d28e88a24 - Adding mana symbol to trigger description 2014-01-10 23:01:04 +00:00
Chris
d937a397a7 [maven-release-plugin] prepare for next development iteration 2014-01-10 14:01:32 +00:00
Chris
c9daed2056 [maven-release-plugin] prepare release forge-1.5.9 2014-01-10 14:01:20 +00:00
Chris
804c36e8b7 Preparing the changes.txt file for the next beta build and release. 2014-01-10 13:47:11 +00:00
drdev
7274d46865 Increase maximum list box auto size width 2014-01-10 04:36:12 +00:00
drdev
feaec2eb6d Auto size ListChooser dialogs to fit items if possible 2014-01-10 04:30:23 +00:00
drdev
753c0731a3 Fix performance of list boxes
Skin border for list on ListChooser dialogs
2014-01-10 03:01:52 +00:00
drdev
9a6b1822a9 Ensure application icon updated when skin changed 2014-01-10 01:17:04 +00:00
Sloth
db5e8316ec - Added AI logic to Ward Sliver. 2014-01-09 22:54:04 +00:00
drdev
513b9db9b5 Update CHANGES.txt 2014-01-09 19:33:26 +00:00
drdev
04248f0166 Refactor skinning logic to avoid caching components, allowing memory to be cleared and improving performance 2014-01-09 19:05:51 +00:00
Chris
c60c041012 Added new card names to changes.txt. 2014-01-09 15:02:49 +00:00
swordshine
f10ec1751f - Converted Old Man of the Sea
- Added Rootwater Matriarch
2014-01-09 13:46:14 +00:00
drdev
561d4deb32 Move graphics functions to static FSkin class 2014-01-09 03:19:30 +00:00
Sloth
3e552ac655 - Fixed Gilt-Leaf Archdruid. 2014-01-08 15:49:04 +00:00
Chris
12d8b289e5 Added new card names to changes.txt. 2014-01-08 14:54:57 +00:00
swordshine
bcf2abdbbc - Comment out unused complex counting methods 2014-01-08 14:28:56 +00:00
swordshine
8747c163ee - Updated scripts 2014-01-08 14:12:07 +00:00
swordshine
458bd8f964 - Updated scripts 2014-01-08 14:02:12 +00:00
swordshine
19a34ba730 - Updated scripts 2014-01-08 13:59:22 +00:00
swordshine
16294fe54a - Combine GreatestPowerYouControl and GreatestPowerYouDontControl 2014-01-08 13:55:22 +00:00
swordshine
76cdfa9759 - Converted Count$LowestLibrary to playXCount 2014-01-08 12:52:09 +00:00
swordshine
fc508b3a27 - Converted LowestLifeTotal 2014-01-08 12:32:02 +00:00
swordshine
a4c2fa3797 - Converted Psychic Transfer for multiplayer 2014-01-08 12:26:14 +00:00
swordshine
d1575996b4 - Converted Liu Bei, Lord of Shu to script 2014-01-08 11:38:50 +00:00
swordshine
75293299ed - Update scripts for AllM12Empires (should fix some corner cases) 2014-01-08 11:22:27 +00:00
swordshine
e50f31fb5f - Update scripts for Visions of Beyond 2014-01-08 11:05:26 +00:00
drdev
e47cdebf14 Prompt to save changes of previous deck before loading another deck 2014-01-08 04:17:26 +00:00
drdev
85eaf866d9 Avoid unnecessary reloading of deck if closing deck 2014-01-08 04:04:23 +00:00
swordshine
800a9b6854 - Added Storage Matrix 2014-01-08 03:54:03 +00:00
drdev
94eb94508c Reload deck if you choose not to save when leaving screen 2014-01-08 03:49:22 +00:00
drdev
02a49149fa Revert previous change 2014-01-08 03:11:47 +00:00
drdev
80308ab621 Prevent prompting to save deck when switching away from editor 2014-01-08 02:35:39 +00:00
drdev
cb055269d3 Prompt before leaving draft 2014-01-08 02:06:17 +00:00
drdev
1c6ccbb20a Make end turn button automatically pass priority if possible, and otherwise not end turn 2014-01-08 01:33:02 +00:00
drdev
764310e08b Fix issues with Dock button hovering 2014-01-08 00:42:54 +00:00
swordshine
673fd2dae3 - Fixed Sphinx of Jwar Isle 2014-01-07 05:35:58 +00:00
drdev
5fe57fcb11 Improve Sealed Deck button usability and support canceling prompt for number of booster packs 2014-01-07 04:08:31 +00:00
drdev
0a9617b0c4 Improve captions on Deck Editors 2014-01-07 03:29:46 +00:00
drdev
6b06a6f274 Prevent being able to add more cards to deck in quest deck editor than you own
Show inventory when in sideboard mode in quest deck editor
2014-01-07 01:59:53 +00:00
Chris
e5c3d4595c Added new card names to changes.txt. 2014-01-06 15:11:55 +00:00
swordshine
ab0a18cc64 - Added Static Orb 2014-01-06 11:46:41 +00:00
drdev
b534c4bbcd Make Deck Editor add/remove items smarter
Support adding X copies of card at a time using new GuiChoose.getInteger API
2014-01-06 08:51:30 +00:00
Agetian
0ee95fae76 - Randomize Card Art is no longer an option and is the default functionality instead (since there is no true support for distinguishing cards with different art in Forge, the game will randomize the card art that appears on cards with multiple available pictures, most notably basic lands, in each match). 2014-01-06 05:33:26 +00:00
Sol
dcb8c54e4c - Updaating Crypt Sliver description 2014-01-05 23:19:47 +00:00
drdev
802aed63df Refactor context menu building logic for editor tables 2014-01-05 22:26:01 +00:00
Sol
ba7cd330e6 - Updating trigger description for Mana Symbol 2014-01-05 22:23:48 +00:00
drdev
adeec5d6a9 Refactor incremental search (FindAsYouType) into ItemView class 2014-01-05 19:59:07 +00:00
drdev
8b0e20aa9e Create DeckManager 2014-01-05 19:21:15 +00:00
Agetian
a22a3dc849 - Fixed the description of Drain Life's spell ability (now it includes the first part of Oracle text which says to spend only black mana on X). 2014-01-05 19:05:35 +00:00
drdev
f220aef52a Make dialog backdrop appear over top of navigation bar 2014-01-05 17:15:20 +00:00
drdev
0175cc1a26 Prevent Alt key opening Forge menu while dialog open 2014-01-05 17:01:11 +00:00
drdev
59b6e092c9 Use new backdrop panel behind FDialog that's more transparent than overlay panel 2014-01-05 16:46:21 +00:00
drdev
ceb7b13a8e Refactor ComponentSkin.skins to compSkins in FSkin 2014-01-05 15:50:13 +00:00
Agetian
f68f8ba157 - Fix a bug that caused the deck subtitle not to be updated when the quest mode deck editor was turned to sideboard mode.
- Fix a bug that allowed the Import button to be used in quest mode deck editor.
2014-01-05 11:33:26 +00:00
drdev
19a58c309e Update CHANGES.txt 2014-01-05 08:34:57 +00:00
drdev
62479e1de6 Use reveal list dialog when revealing cards 2014-01-05 08:29:41 +00:00
drdev
04dde1c18d Fix crash when closing Spell Shop 2014-01-05 08:23:22 +00:00
Agetian
c7450034f5 - Restored the assign random card edition in randomly generated decks functionality. 2014-01-05 08:08:58 +00:00
drdev
9140dab9f1 Ensure animation card panels are disposed 2014-01-05 08:08:46 +00:00
drdev
8f4d3792c6 Dispose CardPanels as they are removed from CardPanelContainer 2014-01-05 08:00:30 +00:00
drdev
9d73236812 Support disposing of component skins when that component's dialog or screen is closed 2014-01-05 07:33:17 +00:00
swordshine
fbc0404f94 - Fixed CostRemoveCounter 2014-01-05 07:20:46 +00:00
Agetian
a2050ed3e6 - Removing an erroneously committed empty method. 2014-01-05 06:06:23 +00:00
drdev
0d2c13bf80 Clear images from cache when switching screens to reduce memory usage
Dispose of card panels when each game ends to reduce memory usage
2014-01-05 06:05:10 +00:00
Agetian
39061439dc - Restored the "Use Random Card Art" functionality. Implementation may be suboptimal with the latest changes in mind, feel free to reimplement in a better way if you know how (do NOT randomly remove though!). 2014-01-05 06:03:30 +00:00
drdev
d9e5d93265 Add standard API for setting default focus on FDialog 2014-01-05 03:30:24 +00:00
Agetian
b3bec906ef - Fix the bug that causes the sideboard not to work correctly when the sideboard is initially empty. 2014-01-04 18:38:50 +00:00
drdev
6d969373b9 Improve list reveal dialogs to allow Escape key and clicking close button
Improve title for "AI can't play cards well" dialog
2014-01-04 17:42:37 +00:00
drdev
c8ac6b9f2d Prevent ListChooser getting caught in infinite loop of reprompting 2014-01-04 16:27:24 +00:00
drdev
45d7b43aa6 Prevent filter widgets being hidden when opening Workshop 2014-01-04 05:44:33 +00:00
drdev
e954b40d3c Skin ListChooser 2014-01-04 05:27:28 +00:00
drdev
07f94f9d42 Ensure filters shown when Ctrl+F used to focus search field 2014-01-04 03:16:20 +00:00
drdev
18809bc918 Prevent updating item manager view if filters didn't change
Focus item manager when Deck Editor opened and when filters hidden/shown
2014-01-04 03:07:14 +00:00
drdev
85d32d49e2 Update CHANGES.txt 2014-01-04 02:55:55 +00:00
drdev
64459b414e Support hiding filters 2014-01-04 02:52:05 +00:00
drdev
f0ffeefe24 Move filter items to "Add" submenu and add "Reset Filters" option to main Filters menu 2014-01-03 03:47:19 +00:00
drdev
df022d5df7 Fix size of Commander Deck Editor button 2014-01-03 02:52:31 +00:00
drdev
a2b75c2072 Prevent showing File menu in Spell Shop 2014-01-03 02:46:53 +00:00
drdev
e7a9492200 Prevent crash when switching away from then back to Spell Shop 2014-01-03 02:25:42 +00:00
Agetian
3133dbf8b4 - A better fix for the attacker icon not appearing (getting rid of GUI calls in game.Combat and moving them over to gui.InputAttack) 2014-01-02 06:51:30 +00:00
Agetian
8fd2ffc93e - Getting rid of UI event duplication for declaration of blockers. 2014-01-02 06:42:58 +00:00
Agetian
39675d465d - Fixed the attacker icon not appearing automatically when a card with "attacks each turn if able" is declared.
- Added the UI event calls to Combat.addAttacker and Combat.addBlocker (this fixes the bug mentioned above). Not sure if this may lead to a duplication of events when declaring attackers/blockers manually, please review.
2014-01-02 06:40:28 +00:00
drdev
6e969ddaaa Update CHANGES.txt 2014-01-01 23:50:15 +00:00
drdev
a9f4009e43 Prevent marking deck as modified when changing sections
Update item manager caption when changing section
Show full catalog when in sideboard section
Avoid hiding title and save buttons when changing sections
2014-01-01 23:43:14 +00:00
drdev
b28b5e1ec9 Add File menu and keyboard shortcuts to Deck Editor 2013-12-31 21:44:45 +00:00
drdev
f4b9dd2f34 Update buttons on concede, restart, and exit prompts 2013-12-31 20:00:51 +00:00
drdev
40c651cd04 Apply anti-aliasing to FDialog border on Windows where it doesn't cause issues 2013-12-31 19:50:02 +00:00
drdev
b6d6da88e8 Skin more message/option/input dialogs
Prevent prompting twice when switching away from modified card in Workshop
2013-12-31 19:30:26 +00:00
drdev
7f95e5f583 Add prompts to confirm buying and selling items in Spell Shop 2013-12-31 17:16:40 +00:00
Maxmtg
e179aa404c setLife uses no gui 2013-12-31 05:53:06 +00:00
Maxmtg
4235a6906e remove isHuman from dig effect 2013-12-31 05:42:47 +00:00
Maxmtg
618cd6415d visited the quest ante code 2013-12-31 05:19:59 +00:00
Maxmtg
9d458ce8c5 still needed an originalDeck in RegisteredPlayer (to restart a match) 2013-12-31 05:08:02 +00:00
Sol
311306e986 - Fixing Mana symbol for Ogre Savant 2013-12-31 03:57:12 +00:00
Sol
a1688a7404 - Some cleanup with ante fixes. Quest mode midgame ante behavior restored in a more generalized fashion 2013-12-31 03:26:33 +00:00
Maxmtg
da421bc5f8 removed some duplicated code 2013-12-30 21:36:24 +00:00
Maxmtg
db8a71cca2 changed how ante works 2013-12-30 21:17:14 +00:00
swordshine
c8352e4968 - Remove phasedOut cards from combat 2013-12-30 14:30:34 +00:00
Chris
1d8aec2c3b Added new card names to changes.txt. 2013-12-30 13:56:56 +00:00
swordshine
459c2cfae4 - Missing Oracle 2013-12-30 01:41:38 +00:00
swordshine
32c52578e2 - Added Bludgeon Brawl 2013-12-30 00:56:08 +00:00
swordshine
eccadbf6fc - Added Nullstone Gargoyle 2013-12-30 00:39:50 +00:00
swordshine
8d99d2032d - Fixed Avacyn, Angel of Hope 2013-12-30 00:38:55 +00:00
Maxmtg
121db288c1 into single thread 2013-12-29 17:16:24 +00:00
Agetian
b8b3fff684 - Changed the representation of booleans in AI profile config files to match the preference file conventions.
- Sorted the options in AI profile config files in alphabetical order.
2013-12-29 08:17:32 +00:00
Agetian
33a08f67ec - Changed the way AI cheating works. The old option "Smooth AI Land" is now replaced with a global toggle "Enable AI Cheating" which allows the AI personalities (profiles) that have various cheat options set in them to utilize those cheat options to gain card advantage over the human.
- Currently there is only one cheat option (CHEAT_WITH_MANA_ON_SHUFFLE) which works like the old Smooth AI Land option by allowing the AI to cheat-shuffle appropriate lands into appropriate places in the library to minimize mana lock.
- Both default AI personalities (Default.ai and Reckless.ai) currently have CHEAT_WITH_MANA_ON_SHUFFLE set to True, which emulates the old Smooth AI Land functionality: if the "Enable AI Cheating" option is enabled, both profiles will cheat-shuffle. If disabled, neither profile will cheat-shuffle.
- Overall, it is now possible to set AI cheating on a per-profile basis and it's also possible to globally disable AI cheating even for personalities that normally do cheat.
- When adding new AI cheating options, don't forget to check for the value of the global toggle which is FPref.UI_ENABLE_AI_CHEATS.
2013-12-29 07:42:07 +00:00
Agetian
dffc1b2a42 - NetBeans settings loss related to expanding tabs is killing me :\ 2013-12-29 06:19:01 +00:00
Agetian
55aa4350b9 - Fixed the Phasing icon not appearing immediately when the card phases in/out.
- Added support for an additional sound effect that is played when a card phases in/out (phasing.wav is played if available).
- Added the "CHEAT_WITH_MANA_ON_SHUFFLE" option to the stock AI profiles (not used yet, to be implemented soon).
- Organized the AI profile config files a little bit by sorting the options.
2013-12-29 06:13:42 +00:00
drdev
40bfde20fa Cleanup Quest and Deck lister display
Fix Quest renaming
2013-12-28 22:28:59 +00:00
drdev
8ceda2b07b Use skinned dialog for new quest dialogs 2013-12-28 20:57:34 +00:00
drdev
a592a44647 Support input FOptionPane 2013-12-28 20:23:41 +00:00
Chris
2a9b59f935 Added new card names to changes.txt. 2013-12-28 14:50:01 +00:00
swordshine
5ed0549dc4 - Fixed devModePlaneswalkTo 2013-12-28 10:29:02 +00:00
swordshine
158da7f60f - Fixed Phyrexian Dreadnought 2013-12-28 06:56:34 +00:00
swordshine
58e529097f - Added Debt of Loyalty, Matopi Golem, Skeleton Scavengers, and Soldevi Sentry 2013-12-28 05:08:39 +00:00
Maxmtg
2de5beb6d1 something is no longer wrong with 2 piles separation in fact or fiction 2013-12-28 00:17:50 +00:00
Chris
cb5df407fd Cleared out the changes.txt file, now ready for new material. 2013-12-27 22:02:48 +00:00
Chris
8fd0e873c7 [maven-release-plugin] prepare for next development iteration 2013-12-27 17:42:27 +00:00
Chris
fe9ff2f4f9 [maven-release-plugin] prepare release forge-1.5.8 2013-12-27 17:42:15 +00:00
Chris
bf05d850fb Preparing the changes.txt file for the next beta build and release. 2013-12-27 17:29:36 +00:00
Chris
266cbd5dde Added new card names to changes.txt. 2013-12-27 17:26:08 +00:00
swordshine
e222c0f7a9 - Added Pools of Becoming 2013-12-27 12:59:31 +00:00
swordshine
12e80be6d1 - Fix NewGame trigger not working 2013-12-27 12:31:21 +00:00
swordshine
5efb2c2e40 - Fixed DigEffect 2013-12-27 11:34:05 +00:00
swordshine
2e574caac6 - Another fix to avoid overriding 2013-12-27 10:32:07 +00:00
swordshine
74b7a40763 -Fixed Selesnya Loft Gardens 2013-12-27 10:30:32 +00:00
Maxmtg
1ede72b64a ai cost decisions now use visitors 2013-12-27 09:26:39 +00:00
Maxmtg
c45fe42129 prerequisites to pay costs using visitor pattern (to remove AI decision-making and gui-specific inputs from the concrete cost classes) 2013-12-27 08:42:44 +00:00
drdev
ddaffcf7a5 Prevent glitchy dialog on Linux
Transition more message/confirm dialogs to skinned look
2013-12-27 05:59:29 +00:00
swordshine
0d28620ee3 - Fixed a typo in Journey of Discovery 2013-12-27 00:28:23 +00:00
Chris
bbab149d3c Added new card names to changes.txt. 2013-12-26 14:36:00 +00:00
Maxmtg
d4e193f39a ensure that limited games won't pop the AI cannot play cards window 2013-12-26 07:25:16 +00:00
swordshine
bd4dd064be - BNG: Added Kiora, the Crashing Wave 2013-12-26 05:47:19 +00:00
Agetian
2efb70e3b5 - Stupid NetBeans space/tab settings were broken again, fixing. 2013-12-26 05:33:47 +00:00
Agetian
92668c3a2a - Adding an AI profile property for cheat shuffling (to be used soon). 2013-12-26 05:31:31 +00:00
Agetian
3915c3ac2c - Fix the dialog window popping up about unplayable cards in the AI deck in Sealed Deck, Booster Draft, and Quest modes. 2013-12-26 05:22:46 +00:00
Chris
51e521fbb3 Added new card names to changes.txt. 2013-12-25 15:03:12 +00:00
Maxmtg
a81a666b81 move haunt Ai to a separate file
remove isComputer method
2013-12-25 08:21:25 +00:00
swordshine
69a9f0fe43 - Added Plunge into Darkness 2013-12-25 08:14:19 +00:00
Maxmtg
9262ad4d82 make newGame method private, non-static 2013-12-25 08:07:33 +00:00
Maxmtg
b6270a71bb dissolve gameNew, move ai cheating to that very class, improve ante messages
remove lose by milling option
2013-12-25 08:03:20 +00:00
Maxmtg
e31d2ae0d1 fix proliferate not working on creatures 2013-12-25 05:49:00 +00:00
Maxmtg
77510ac6e3 arrange inputs hierarchy 2013-12-25 05:42:49 +00:00
swordshine
0f1e88bb25 - Converted Amplify to replacement effects 2013-12-25 04:50:26 +00:00
swordshine
c5a42f03e6 - Update more scripts 2013-12-25 04:07:37 +00:00
swordshine
de1124c19e - Update 3 scripts 2013-12-25 00:33:58 +00:00
drdev
60ba4bfec6 Add back FDialog custom paint code 2013-12-24 19:06:10 +00:00
drdev
6372242b0b Temporarily comment out FDialog custom paint code 2013-12-24 19:05:17 +00:00
drdev
9a8c4a1c8c Make Left/Right/Home/End keys shift focus as expected between buttons in FOptionPanes 2013-12-24 18:50:09 +00:00
drdev
891db98700 Close FDialogs when Escape key pressed 2013-12-24 18:38:11 +00:00
drdev
f7d1b73a6c Move setShape call from paint to resize listener rather to hopefully fix Linux issue
Make FDialog border non-rounded if setShape isn't supported (Mac)
2013-12-24 18:11:09 +00:00
swordshine
7a906fdc03 - Update more scripts 2013-12-24 13:21:40 +00:00
swordshine
e7046c9bf5 - Update more scripts 2013-12-24 10:54:44 +00:00
swordshine
8d87b0256b - Update more scripts 2013-12-24 10:30:26 +00:00
Maxmtg
802aa167a9 Added input to select both players and cards for tokens created by Hero of Bladehold 2013-12-24 09:23:14 +00:00
swordshine
b83bd41716 - Update more scripts 2013-12-24 07:06:01 +00:00
swordshine
7c8cb3053a - Update more scripts 2013-12-24 06:24:29 +00:00
swordshine
7c9fb9f6b4 - Update some scripts 2013-12-24 05:21:11 +00:00
swordshine
68989d3e31 - Cleanup 2013-12-24 02:08:18 +00:00
swordshine
51a0745348 - Refactored Entwine ability 2013-12-24 02:06:22 +00:00
drdev
3505a2bcd5 Add FOptionPane
Make FDialog title use skinned font
2013-12-23 23:05:17 +00:00
drdev
0581a5e42b Fix Mac FDialog crash properly 2013-12-23 21:16:40 +00:00
drdev
7ea839334c Fix setShape issue without try/catch block 2013-12-23 17:06:39 +00:00
drdev
be7cce4300 Prevent FDialog crashing on Mac. 2013-12-23 16:46:03 +00:00
drdev
c78251a90e Improve appearance of FDialogs
Make CardListViewer use FDialog and display on screen
2013-12-23 10:45:31 +00:00
Maxmtg
c3c8fd7186 improve random choice of N cards
remove gui calls from choosePile effect
2013-12-23 09:05:50 +00:00
drdev
3d97768f71 Code cleanup 2013-12-23 04:52:08 +00:00
swordshine
f2c93cb5dd - Fixed Maelstrom Nexus and similar cards 2013-12-23 04:37:59 +00:00
drdev
689ef0cf8a Make selection after adding/removing cards from deck behave as expected and improve performance of adding multiple cards at once
Support Ctrl+F in Current Deck pane
Prevent losing multi-selection when using Left/Right arrow keys to switch focus between Catalog and Deck
2013-12-23 03:03:33 +00:00
swordshine
77de4abfd1 - Fixed Battalion Triggers check requirements when resolving 2013-12-23 02:58:10 +00:00
Maxmtg
dc16b75c94 minimize need to player type checks 2013-12-22 23:13:07 +00:00
Maxmtg
3a0fdb4d4f remove gui calls from untap 2013-12-22 22:50:01 +00:00
Maxmtg
32b0f8333d remove calls to gui from game code 2013-12-22 22:36:36 +00:00
drdev
446dfd2257 Add +/- buttons to Deck Editor Quantity column 2013-12-22 20:43:11 +00:00
drdev
faa9947974 Fix so tooltips don't always appear for all columns of table 2013-12-22 19:09:33 +00:00
drdev
c75971e8c5 Get favorites feature working again 2013-12-22 18:20:36 +00:00
Maxmtg
2c850cbe53 clash - gui call replaced by gameAction.notifyOfValue
proliferate - single executor for human and AI, chooseProliferation method added to PlayerController
2013-12-22 18:04:00 +00:00
Chris
a060f5f322 Added new card names to changes.txt. 2013-12-22 15:00:26 +00:00
Maxmtg
5367811a33 moved card prefrences to gui 2013-12-22 13:08:28 +00:00
Maxmtg
7d41ae1331 revert unwanted "preferences" from game-core.
remove gui calls
2013-12-22 13:06:27 +00:00
swordshine
f55abfa576 - Added Tariff 2013-12-22 13:03:31 +00:00
drdev
79b8340b80 Fix bug with sort priorities getting messed up 2013-12-22 10:21:53 +00:00
drdev
e0b7034f97 Fix so favorites appear on top by default when column first clicked 2013-12-22 10:03:57 +00:00
drdev
b79876c959 Support hiding Favorite column from Editor Preferences pane 2013-12-22 09:39:00 +00:00
drdev
e159c25f6d Improve appearance of star icons 2013-12-22 09:07:47 +00:00
drdev
d16eedd1b3 Update CHANGES.txt to mention Deck Editor usability improvements 2013-12-22 08:41:08 +00:00
drdev
a062d61c8d Support marking cards as favorites in catalog 2013-12-22 08:39:20 +00:00
drdev
471e5b5399 Support mana icons 13, 14, 17, 18, 19
Reorganize a couple mana symbols in skin
2013-12-21 22:29:27 +00:00
drdev
577f94a67f Fix firebloom and simpsons mana icons 2013-12-21 22:01:29 +00:00
drdev
f7e05491da Prevent 16/17 mana cost icons being cut off 2013-12-21 21:56:09 +00:00
drdev
2771b4fe1f Fix mana cost rendering in table
Start favorites column
2013-12-21 19:43:52 +00:00
Sol
b5830fac6e - Command Tower taps for Controller Commander's CI, not the owners. 2013-12-21 17:34:25 +00:00
drdev
d8511c92c0 Fix so Pack and Multicolor filter buttons work correctly when only they are toggled off 2013-12-21 17:33:30 +00:00
Sloth
1ea41edd6a - Added the hard quest opponent Abe Sapien 3. 2013-12-21 07:28:38 +00:00
swordshine
4f79f92940 - Converted Sphinx of Jwar Isle to script 2013-12-21 03:16:16 +00:00
swordshine
e13865a966 - Converted "You may discard CARDNAME any time you could cast an instant." to script 2013-12-21 01:08:36 +00:00
Maxmtg
05e1161632 this should prevent some NPE 2013-12-20 20:40:33 +00:00
Sloth
feae947742 - Fixed Tsabo's Decree. 2013-12-20 16:13:44 +00:00
Sloth
9ef69149ee - Fixed preventRunAwayActivations to work with temporary abilities.
- AI updates and code cleanup.
2013-12-20 15:45:27 +00:00
Chris
04ca668807 dded new card names to changes.txt. 2013-12-20 14:46:42 +00:00
Sloth
489af75669 - Updated the quest deck Optimus Prime 3. 2013-12-20 14:44:19 +00:00
swordshine
c8e1aff861 - Update mana replacements in ComputerUtilMana 2013-12-20 08:49:00 +00:00
Maxmtg
373bcd0351 commit 24000 FTW! (remove gui calls from untap) 2013-12-20 08:40:08 +00:00
swordshine
c08e984e35 - Added Hall of Gemstone 2013-12-20 08:06:33 +00:00
drdev
5dee59e8e5 Make Pack stat label appear in color and cmc filters too so each toggle button filter has the same button count and works the same way when left clicking other filters. 2013-12-20 04:57:04 +00:00
drdev
350dbeb12f Add back Pack filter button to Spell Shop 2013-12-20 03:35:32 +00:00
swordshine
fdc2eb66aa - Added Scrambleverse 2013-12-20 00:33:16 +00:00
Maxmtg
ef332e4568 move HumanPlay, PlayerControllerHuman to forge.gui.player package 2013-12-20 00:22:20 +00:00
Maxmtg
93c7d28714 moved limited play to a different package (since they don't belong to game module) 2013-12-19 23:17:04 +00:00
Maxmtg
5d8225f041 eliminate gui calls 2013-12-19 23:15:32 +00:00
Maxmtg
b501c76f7c some more gui calls gone 2013-12-19 23:03:28 +00:00
Maxmtg
97526110d0 removing direct references from game to gui 2013-12-19 22:20:28 +00:00
drdev
f8a4c5db92 Fix Statistics tooltips 2013-12-19 06:22:00 +00:00
drdev
2093d23e24 Add Card CMC toggle button filters
Enhance Filters menu to support adding Color and Type filters
2013-12-19 06:14:49 +00:00
drdev
7e32b752a0 Refactor ItemTable into ItemListView and ItemView base class
Add combo box for selecting view
2013-12-19 04:29:59 +00:00
Sloth
4b02708bb0 - Updated the Sherlock Holmes quest decks. 2013-12-18 15:47:50 +00:00
Chris
9684d69bd1 Added new card names to changes.txt. 2013-12-18 15:07:57 +00:00
swordshine
f6d07944d8 - Converted TokenDoubler to script 2013-12-18 11:38:07 +00:00
swordshine
46796041c7 - Added Primal Vigor 2013-12-18 10:33:44 +00:00
swordshine
196a782f8c - Update 3 scripts 2013-12-18 05:25:41 +00:00
swordshine
4839d05b70 - Update two scripts 2013-12-18 04:27:09 +00:00
drdev
d8b6140692 Refactor previous fix 2013-12-17 22:14:17 +00:00
Sloth
040e51f124 - Fixed two scripts missing a nonToken restriction. 2013-12-17 22:11:07 +00:00
drdev
a24161db2b Support search for non-card items in SpellShop 2013-12-17 21:42:21 +00:00
drdev
117501c511 Fix SpellShop item manager to not crash and support filtering 2013-12-17 21:21:35 +00:00
moomarc
1c122b99e9 - Fixed Rotted Ones Lay Siege 2013-12-17 10:24:15 +00:00
drdev
6970c1e7cf Fix ratio in Current Deck item manager
Add captions before ratios in item managers
Fix padding around header in Current Deck pane
2013-12-16 19:28:33 +00:00
drdev
d56cecdba4 Integrate filters into ItemManager 2013-12-16 09:10:40 +00:00
Sloth
8ebf514dab - Fixed possible NPE in ChooseSourceAi. 2013-12-15 22:36:14 +00:00
Sol
1376f0dde2 - Fixing mana symbols for a few devotion cards 2013-12-15 16:50:42 +00:00
Sloth
dbacf597da - Updated some quest decks. 2013-12-15 11:17:58 +00:00
Sloth
87eaf96fa1 - Fixed Reparations triggering on creatures not on the battlefield. 2013-12-14 21:21:09 +00:00
Sloth
13db59aac8 - Fixed possible source for NPE's in RemoveFromCombatEffect. 2013-12-14 20:56:21 +00:00
Sol
9a46768de6 - Starke is already removed from combat by the GainControl code, and whatever he's destroying should be removed by the destruction code. 2013-12-14 19:02:50 +00:00
Chris
3c5010cebf Cleared out the changes.txt file, now ready for new material. 2013-12-14 15:35:37 +00:00
Sloth
d25b197c09 - Fixed Legion's Initiative. 2013-12-14 07:52:54 +00:00
Chris
b892cfa029 [maven-release-plugin] prepare for next development iteration 2013-12-13 15:54:18 +00:00
Chris
23a012364b [maven-release-plugin] prepare release forge-1.5.7 2013-12-13 15:54:07 +00:00
Chris
ed0f3600f5 Preparing the changes.txt file for the next beta build and release. 2013-12-13 15:39:28 +00:00
drdev
dd4e250432 Fix compilation problem 2013-12-13 08:05:42 +00:00
drdev
2ffb71e053 Code cleanup 2013-12-13 08:04:36 +00:00
Maxmtg
be25cf699c removed input play or draw (for being a special case of InputConfirm)
added optional param to chooseCreatureType
2013-12-13 07:51:27 +00:00
drdev
1cc15fe50c Update CHANGES.txt 2013-12-13 07:40:21 +00:00
drdev
a04e5c1da2 Skin FDialog 2013-12-13 07:29:55 +00:00
Maxmtg
5b18ba9058 Cost.chooseXValue moved to member of CostPart 2013-12-13 07:10:23 +00:00
Maxmtg
753f4d989b renamed InputYesOrNo, removed static methods from it, added a method to confirmPayment to PlayerController 2013-12-13 07:01:30 +00:00
Maxmtg
8960fa20c3 added showAndWait method to InputSyncronizedBase - it holds the long chain of calls to reach Control's input queue. 2013-12-13 06:25:55 +00:00
drdev
38344027ca Show cost payment prompts during ability resolve using Prompt pane instead of a dialog 2013-12-13 06:17:03 +00:00
drdev
90ff1abab8 Code cleanup 2013-12-13 05:48:35 +00:00
drdev
7fe3799ea6 Code cleanup 2013-12-13 05:19:09 +00:00
drdev
5ed6fba085 Code cleanup 2013-12-13 05:04:27 +00:00
drdev
6c3573d854 Use Prompt pane instead of dialogs for cost-related questions 2013-12-13 04:48:31 +00:00
drdev
d00aeffbda Add static "ask" API to InputYesOrNo 2013-12-13 03:07:15 +00:00
drdev
e247b8e368 Update CHANGES.txt 2013-12-13 02:40:36 +00:00
drdev
3d54fd3b7b Prompt Yes or No for optional triggered abilities using Prompt pane rather than dialog 2013-12-13 02:37:09 +00:00
drdev
b19b5daf48 Refactor confirmTrigger into controller class 2013-12-13 02:02:54 +00:00
drdev
f48952aa94 Added notes to CHANGES.txt 2013-12-13 01:06:37 +00:00
drdev
e8d4433bb3 Tweak follow up logic after auto payment 2013-12-13 00:59:45 +00:00
drdev
84b0e17572 Fix so you're still prompted for a color when paying cards that care what colors are spent to cast them 2013-12-12 14:53:17 +00:00
drdev
e4ba6f56dd Fix so GUI thread not locked while waiting for Auto payment 2013-12-12 12:57:08 +00:00
drdev
bdfd2e9adf Avoid prompting for color when using "Add one mana of any color" effect to pay for a colorless cost 2013-12-12 12:43:50 +00:00
drdev
849655cb2f Fix so Auto payment occurs in Game thread 2013-12-12 11:49:50 +00:00
swordshine
1c391a7c7e - Fixed Varolz 2013-12-12 11:36:41 +00:00
Sloth
cf1990ddf7 - Fixed Ragged Veins. 2013-12-11 15:04:35 +00:00
drdev
fad3ea497d Code cleanup 2013-12-11 04:05:17 +00:00
drdev
f586525a61 Ensure index in hand zone updated when card drag dropped into new position 2013-12-10 16:27:11 +00:00
Chris
832a290778 Added new card names to changes.txt. 2013-12-10 13:26:11 +00:00
swordshine
ecbba9f1c0 - Added Sheltering Ancient 2013-12-10 13:10:32 +00:00
drdev
afeb4eb815 Fix so canceled spells return to same place in your hand as they were cast from 2013-12-10 02:50:24 +00:00
drdev
d0b5e78f7a Code cleanup 2013-12-10 01:13:34 +00:00
drdev
1837234650 Revert change that prevented a spell moving on the stack until after prerequisites were met 2013-12-10 00:24:21 +00:00
drdev
c65031ea31 Avoid removing source card from lists 2013-12-10 00:17:46 +00:00
Chris
14a7176325 Added a new piece to the Known Issues section. 2013-12-09 19:23:32 +00:00
swordshine
a0c3a1eb05 - Clones are able to copy LevelUp abilities 2013-12-09 04:03:02 +00:00
drdev
f7af3865b7 Allow canceling discarding cards to pay cost 2013-12-09 00:03:18 +00:00
drdev
51a0c08042 Prevent card exiled to pay its own additional cost 2013-12-08 23:47:53 +00:00
drdev
abc2118a77 Code cleanup 2013-12-08 23:18:40 +00:00
drdev
776ac3ee2b Prevent card discarded to pay its own additional cost 2013-12-08 23:12:07 +00:00
drdev
63eaec8348 Code cleanup 2013-12-08 22:50:59 +00:00
drdev
ddb8464c1c Code cleanup 2013-12-08 22:22:44 +00:00
drdev
29ef7a7d3b Code cleanup 2013-12-08 22:14:19 +00:00
drdev
49aa0cc459 Code cleanup 2013-12-08 22:10:03 +00:00
drdev
cdf1b44cb0 Check static abilities when adding card to zone from nowhere (such as using Dev tools) 2013-12-08 18:47:31 +00:00
drdev
61fe9f2282 Code cleanup 2013-12-08 17:49:49 +00:00
Maxmtg
0b9d254e12 fix NPE caused by null predicate 2013-12-07 23:12:37 +00:00
Maxmtg
77b241eb68 remove another toString method from manacost, update references to use better ways 2013-12-07 22:18:52 +00:00
Sloth
b98ae5844c - Fixed type of Stern Judge. 2013-12-07 21:28:45 +00:00
drdev
507ce6f220 Ensure OK button gets initial focus in DualListBox if remainingSources matches source list count 2013-12-07 20:08:03 +00:00
drdev
4d2a27f7e8 Prevent adding cards in DualListBox using spacebar if add button disabled
Prevent selecting cards to discard with Abandon Hope if 0 cards should have been allowed
2013-12-07 19:54:57 +00:00
drdev
a90841a6b6 Fix conditions under which Auto button enabled on DualListBox 2013-12-07 19:39:33 +00:00
drdev
e9bb6e42f6 Prevent adding cards in DualListBox once specific target reached 2013-12-07 19:30:42 +00:00
drdev
ed4b11d9ef Prevent focusing OK button if DualListBox has no specified count needed 2013-12-07 19:10:16 +00:00
drdev
c115f01edf Fix so OK button in DualListBox gets focus when it becomes enabled 2013-12-07 18:52:10 +00:00
drdev
8fd6bbef3b Code cleanup 2013-12-07 18:29:49 +00:00
drdev
8fa68731d1 Fix so mana refunded when using Auto to pay non-X cost then canceling at X prompt 2013-12-07 18:25:29 +00:00
drdev
b6b586a865 Make {X} appear before generic on card overlays 2013-12-07 17:38:52 +00:00
Chris
457ba444f1 Added new card names to changes.txt. 2013-12-07 14:36:10 +00:00
Sloth
31a367c434 - Added Praetor creature type. 2013-12-07 09:43:13 +00:00
swordshine
e83eb92948 - Added Fight or Flight 2013-12-07 01:50:02 +00:00
Sloth
5ee779846d - Fixed 2 more LKI problems. 2013-12-06 17:11:54 +00:00
Sloth
7a3ecf8e38 - Fixed LKI problems with Mercy Killing and similar cards. 2013-12-06 17:06:12 +00:00
Chris
9e2c96a18e Minor change to the changes.txt file. 2013-12-06 15:23:43 +00:00
Sloth
6550fa28ca - Fixed a bug in DamagePreventAi. 2013-12-06 14:47:30 +00:00
drdev
de9ce7ebb9 Mention recent game play usability improvements in CHANGES.txt 2013-12-06 07:26:54 +00:00
drdev
caae78b6ee Add basic Undo support for untapping mana sources that were tapped when not paying an active mana cost 2013-12-06 07:16:04 +00:00
Maxmtg
495701c46c tap-untap effect is indifferent to playertype 2013-12-06 06:46:24 +00:00
Maxmtg
ed99ce1694 remove gui calls from add/remove counters effects 2013-12-06 06:26:49 +00:00
Maxmtg
51a37e896d move counters no longer uses gui 2013-12-06 06:10:51 +00:00
Maxmtg
b431315e88 removed some gui calls 2013-12-06 05:49:11 +00:00
drdev
ea59f120da Prevent cards disappearing from your hand while paying for them or picking targets 2013-12-06 05:11:36 +00:00
drdev
9bd30e8d5d Code cleanup 2013-12-06 04:37:39 +00:00
drdev
39d1ef7e13 Add Escape as shortcut for Cancel in Prompt even if OK button has focus 2013-12-06 04:24:49 +00:00
drdev
83a1b70efe Ensure game ends if you concede while in the middle of playing a spell/ability 2013-12-05 02:26:06 +00:00
drdev
01b4f803a8 Code cleanup 2013-12-05 02:25:19 +00:00
drdev
5c31890066 Code cleanup 2013-12-05 02:02:34 +00:00
drdev
be7f4e5192 Code cleanup 2013-12-05 01:37:35 +00:00
drdev
4124c45875 Code cleanup 2013-12-05 01:18:28 +00:00
drdev
06e785c75a Code cleanup 2013-12-05 01:10:32 +00:00
drdev
2f384c079c Code cleanup 2013-12-05 00:56:57 +00:00
drdev
e4adada542 Code cleanup 2013-12-05 00:50:42 +00:00
drdev
efa86891cc Code cleanup 2013-12-05 00:38:04 +00:00
drdev
bcc20bb774 Return focus to OK button when returning to match screen 2013-12-05 00:31:41 +00:00
drdev
937a095209 Make runAsAi private 2013-12-05 00:14:06 +00:00
Maxmtg
1661228168 fix npe in PlayerControllerHuman 2013-12-04 21:28:11 +00:00
Chris
b17b9eb4e1 Added new card names to changes.txt. 2013-12-04 13:31:37 +00:00
Maxmtg
2ffa1510ed Player belongs to game, it cannot have references to any concrete PlayerController 2013-12-04 07:46:30 +00:00
swordshine
d1ef107b48 - Added Chorus of the Conclave 2013-12-04 07:05:50 +00:00
drdev
aa48e9cacc Fix so Ok button in DualListBox dialog receives focus as soon as it's been enabled so spacebar will activate it 2013-12-04 03:44:32 +00:00
drdev
3c0bfa7843 Remove extra blank line before "Pay Mana Cost" with optional payment prompt 2013-12-04 02:58:54 +00:00
drdev
803b6331f4 Fix so abilities using InputPayManaExecuteCommands work with Auto button 2013-12-04 02:49:26 +00:00
drdev
fe8af8705d Fix crash when determining if mana costs can be paid for SpellAbilities with no source card 2013-12-04 01:51:44 +00:00
drdev
624365cda3 Prevent AI taking over if exception thrown while AI controller temporarily set 2013-12-04 01:33:42 +00:00
Maxmtg
4fb59b6071 give spelldescriptions to card that offer 'generic' choices 2013-12-03 09:04:09 +00:00
Maxmtg
d15948e44b remove refs to gui and playertype in ChooseGenericEffect 2013-12-03 08:48:19 +00:00
Maxmtg
bf629ff518 choose color now uses player controller to decide.
removed calls to gui from ability executing classes
adjust visibility of CardFace methods
2013-12-03 08:27:44 +00:00
Maxmtg
ee9ee7f207 moved evaluator away from core 2013-12-03 06:56:13 +00:00
Maxmtg
311d4091ff remove runAsAi from playercontroller (it's meant to be indifferent to playercontrollers, and PlayerControllers descendants don't know of thier siblings' existance) 2013-12-03 06:34:29 +00:00
Sol
4e79f508c0 - Fixed issue with formatted mana being used for level up cost 2013-12-03 04:19:31 +00:00
swordshine
90828e6eab - Fixed a bug related to Commander games 2013-12-03 00:58:35 +00:00
Chris
7d3afc35cc Added a fluff piece to the changes.txt file.
Added new card names to changes.txt.
2013-12-02 14:58:46 +00:00
swordshine
6f4c046732 - Added Mana braces for three cards 2013-12-02 13:05:56 +00:00
swordshine
503a8da70f - Added Caller of the Hunt 2013-12-02 12:24:43 +00:00
drdev
1cf9a005d9 Fix places that assumed old format of ManaCost.toString() to either call new getCostString() function or assume {G} formatting
This fixes bugs with Rout ("may cast as instance if you pay 2 more" cards)
This also fixes bugs with Rune Snag ("unless player pays X" cards)
2013-12-02 08:52:30 +00:00
drdev
fe04d56568 Code cleanup 2013-12-02 07:31:12 +00:00
drdev
93b6ac15dd Ensure enters the battlefield triggered abilities are visible on stack 2013-12-02 07:06:50 +00:00
drdev
500632515c Mention Auto button and other game usability improvements in CHANGES.txt 2013-12-02 06:15:26 +00:00
drdev
598d2da9d6 Prevent mana not deducting from pool when manually paying mana costs 2013-12-02 05:56:18 +00:00
drdev
05f7a9b30a Prevent being prompted to decide how to pay mana when clicking Auto by temporarily using AI controller 2013-12-02 05:33:44 +00:00
drdev
0b6be04428 Add "Auto" button for automatically paying a mana cost
Code cleanup
2013-12-02 03:07:19 +00:00
Sol
dcb9553807 - Added Mana braces for Level up Ability and Insidious Bookworms 2013-12-02 01:03:38 +00:00
Chris
c9c9be68b1 Added new card names to changes.txt. 2013-12-01 13:50:11 +00:00
swordshine
53e6671143 - Added Call to Arms 2013-12-01 05:34:21 +00:00
drdev
3d063d8188 Code cleanup and refactoring 2013-12-01 03:50:47 +00:00
drdev
8d3633fe63 Code cleanup 2013-12-01 03:27:17 +00:00
drdev
ee7d64c533 Code cleanup 2013-12-01 03:24:22 +00:00
drdev
5009ebe0ab Code cleanup 2013-12-01 03:21:51 +00:00
drdev
9b2f3f3108 Tweak fix for not prompting if can't attack 2013-12-01 03:14:19 +00:00
drdev
9ca9dc797e Don't prompt to declare blockers if no creatures can block 2013-11-30 22:01:04 +00:00
drdev
2ffc18cc89 Skip combat phases if player has no creatures that can attack 2013-11-30 20:57:30 +00:00
drdev
cfeeda3c93 Cleanup whitespace 2013-11-30 18:50:17 +00:00
drdev
a2277bc2d4 Cleanup whitespace 2013-11-30 18:40:40 +00:00
drdev
694e4e7988 Fix misspelling 2013-11-30 18:39:28 +00:00
drdev
1c0e786489 Code cleanup 2013-11-30 18:31:18 +00:00
drdev
7420e94876 Rename CMessage to CPrompt to better match tab caption 2013-11-30 18:19:22 +00:00
drdev
73c921bb95 Increase contrast between Inactive color and background texture in Journeyman theme 2013-11-30 18:00:13 +00:00
Chris
7a22f4f74a - Added a fluff piece to the changes.txt file.
- Cleared out the changes.txt file, now ready for new material.
2013-11-30 15:15:56 +00:00
drdev
cdb76d344e Fix display of 0 loyalty planeswalker abilities 2013-11-30 07:26:10 +00:00
drdev
6cbded92cb if colorless filtered out ensure phyrexian cards don't appear unless at least one of their colors is selected 2013-11-30 07:04:21 +00:00
drdev
bc032a074c Optimize logic for no cost cards 2013-11-30 06:46:20 +00:00
drdev
89a4afdc08 Prevent cards with no mana cost showing up if they're color is filtered 2013-11-30 06:28:36 +00:00
drdev
be6266d633 Improve filter logic to support showing playable hybrid and phyrexian cards even if filtering out colors present in the card's mana cost that aren't needed to cast it 2013-11-30 05:45:30 +00:00
drdev
705264ba9a Ensure multicolor filter selected after right-clicking a color filter 2013-11-30 00:56:05 +00:00
drdev
a0b181e6fe Don't filter out multicolor cards after right-clicking a color filter 2013-11-30 00:51:09 +00:00
drdev
df50a0a38d Support showing only colorless or multicolor cards 2013-11-30 00:27:47 +00:00
Chris
abd182e17f [maven-release-plugin] prepare for next development iteration 2013-11-29 15:06:44 +00:00
Chris
312c15d824 [maven-release-plugin] prepare release forge-1.5.6 2013-11-29 15:06:32 +00:00
Chris
3f0cdc9b19 - Preparing the changes.txt file for the next beta build and release. 2013-11-29 14:52:38 +00:00
drdev
5d5c42c843 Mention Compact Prompt in CHANGES.txt 2013-11-29 06:57:36 +00:00
drdev
1a8fe19c88 Fix so multi-color cards containing a filtered color no longer show up in catalogs 2013-11-29 06:52:51 +00:00
drdev
7e37495b49 Hide checkbox to enable "Stack Card View" setting which isn't ready yet 2013-11-29 06:23:29 +00:00
drdev
3210f06931 Revert so command zone shown for all game types 2013-11-29 05:08:25 +00:00
drdev
a457ff539d Convert hide prompt header setting into compact prompt setting that also controls font size 2013-11-29 04:51:51 +00:00
drdev
afbcd90ffc Don't switch to Combat pane unless an attacker is declared 2013-11-29 04:37:35 +00:00
Maxmtg
8acdc6c458 fix name card effect 2013-11-29 04:37:23 +00:00
drdev
4ed8f69da1 If switched to Combat pane when combat begins, switch back to previous pane when combat ends 2013-11-29 04:28:05 +00:00
Maxmtg
90fe657da6 refactor: move isCommandZoneNeeded to gameType (java enums are far better than c#'s ones :) 2013-11-29 04:03:20 +00:00
drdev
91fe7b516c Show turn number in prompt message 2013-11-29 03:54:04 +00:00
drdev
e3851e3f1d Reduce padding around Prompt buttons 2013-11-29 03:39:34 +00:00
drdev
301014285f Reduce font size of prompt
Add setting to hide prompt header
2013-11-29 03:33:09 +00:00
drdev
e467f44bf2 Fix so Save and Open dialogs appear over main window 2013-11-29 02:59:22 +00:00
drdev
e8e5ac2ca7 Don't show command zone if game type doesn't need it 2013-11-29 02:51:55 +00:00
drdev
ebb0a4b2bc Mention change to show tabs if multiple on pane in CHANGES.txt 2013-11-28 18:01:50 +00:00
drdev
d21e2666ec Don't hide drag tabs if drag cell has multiple tabs 2013-11-28 17:58:46 +00:00
moomarc
43140b2db3 - Small fix for multiplayer tab labels 2013-11-28 09:02:39 +00:00
Maxmtg
c898e1321f cardreader fix 2013-11-27 07:46:21 +00:00
Maxmtg
4f04cbbcc8 fix hangs when both textfiles and zip are present in cardsfolder 2013-11-26 17:40:30 +00:00
asepetci
f17e857710 updated rankings.txt 2013-11-26 09:01:22 +00:00
drdev
2401b9b1ff Fix display issue with switching away from Game screen then back 2013-11-26 03:31:03 +00:00
drdev
e646ff1d1f Add checkbox to control whether list view or card view for Stack 2013-11-26 01:55:46 +00:00
drdev
9bb800ce43 Start adding card view to Stack, with preference to use current list view 2013-11-26 01:36:42 +00:00
ptx
6859ad2ad9 Some initial game simulation tests 2013-11-25 21:33:24 +00:00
drdev
19152790b2 Prevent dividing by zero when laying out cards 2013-11-24 23:03:51 +00:00
drdev
05751dbe1b Break up files in nonsingleton folder into existing controller and view folders 2013-11-24 21:16:23 +00:00
drdev
f35f6b3edd Cleanup whitespace 2013-11-24 20:56:11 +00:00
Maxmtg
8af7fadbf6 another 2 calls to gui removed 2013-11-24 19:21:30 +00:00
Maxmtg
9190ce038f remove imports of forge.gui from where they are not supposed to be (part 2 of many) 2013-11-24 11:14:51 +00:00
Maxmtg
bda47748f2 remove imports of forge.gui from where they are not supposed to be (part 1 of many) 2013-11-24 10:53:58 +00:00
Maxmtg
96084d7b26 disband forge.card package in gui module 2013-11-24 10:34:45 +00:00
Maxmtg
e96ff73b50 modified spells' canPlayAi, doTrigger and playFromEffect methods used by AI to include aiPlayer as 1st parameter 2013-11-24 10:02:58 +00:00
Hellfish
fcb8c4bdd6 *Fixed Charm of Vigor not having to be bought to be available 2013-11-24 09:44:28 +00:00
Maxmtg
c7c1d2d302 arrange Predicates for CardEdition 2013-11-24 09:35:43 +00:00
Maxmtg
21ea8e377c move card/CardCharacteristics.java to forge.game.card 2013-11-24 09:24:50 +00:00
Maxmtg
e9409d01a6 merged cardfactory into forge.game.card package 2013-11-24 07:17:43 +00:00
Maxmtg
a8c70a903f arrange packages 2013-11-24 07:09:23 +00:00
Maxmtg
c66ca97dfc move game Card class to forge.game.card package to ease further translation to game module 2013-11-24 06:59:42 +00:00
Maxmtg
5b5517ad29 replace TreeMapOfLists with Guava Multimap 2013-11-24 06:45:30 +00:00
drdev
c8de886967 Fix Flash of Insight reminder text 2013-11-23 23:13:54 +00:00
drdev
2ef19583a6 Fix displayed equip cost for M13 ring cycle 2013-11-23 23:06:01 +00:00
drdev
9e0ff5e635 Fix Civic Saber descriptions 2013-11-23 22:54:07 +00:00
drdev
427cef758f Fix Card Detail for Equip and Fortify cards 2013-11-23 22:49:54 +00:00
drdev
1cc1516feb Mention preference to hide reminder text in CHANGES.txt 2013-11-23 21:56:11 +00:00
drdev
daafa3f5c2 Add preference to Hide Reminder Text 2013-11-23 21:55:12 +00:00
drdev
9e20af7603 Cleanup whitespace 2013-11-23 21:37:37 +00:00
Chris
306a160df0 - Added a fluff piece to the changes.txt file. 2013-11-23 14:57:58 +00:00
Hellfish
b5686bcd2f *Removed first attempt code Charm of Vigor code. 2013-11-23 09:43:57 +00:00
Maxmtg
33dd245131 commit empty folders (otherwise eclipse refuses to move files) 2013-11-23 08:46:17 +00:00
Maxmtg
25334c27f1 (minor) remove IZone, rename gameAge to gameStage, remove unused imports 2013-11-23 08:34:14 +00:00
Maxmtg
3f20ebb15b prevent NPE for cases when new card is added 2013-11-23 08:21:49 +00:00
Maxmtg
37849b88e2 clean up core classes 2013-11-23 08:12:40 +00:00
drdev
800b3f8272 Tweak logic for formatting reminder text in italics 2013-11-23 06:39:42 +00:00
drdev
b2ecb3cb52 Mention reminder text formatting in CHANGES.txt 2013-11-23 06:25:27 +00:00
drdev
8a794f2e02 Display reminder text in italics 2013-11-23 06:21:26 +00:00
drdev
3c05e1674f Only show Workshop tab if running in Developer mode (for now) 2013-11-23 06:06:19 +00:00
drdev
a15b3c2a69 More card format fixes 2013-11-23 05:54:25 +00:00
drdev
10c138d64e Format mana batteries and a few other cards 2013-11-23 05:39:42 +00:00
drdev
99d1718498 Tweak formatting on Musician 2013-11-23 05:25:00 +00:00
drdev
515985dead Fix formatting of Volvers and Planar cards 2013-11-23 05:12:53 +00:00
drdev
e30c4532b8 Show regular and variant cards in Workshop 2013-11-23 05:03:07 +00:00
drdev
2ab5f19f79 Fix formatting of a couple cumulative upkeep cards 2013-11-23 04:55:17 +00:00
drdev
f5054558c2 Fix formating of Forecast and other cards 2013-11-23 04:50:31 +00:00
drdev
b726053164 Auto-select first card after doing search if no card otherwise selected 2013-11-23 04:38:34 +00:00
drdev
cc433b6708 Fix so PaperCard for card name doesn't change unless card renamed 2013-11-23 04:19:05 +00:00
drdev
e95e251161 Format mana costs for card scripts 2013-11-23 04:15:06 +00:00
drdev
75edc751f3 Format bestow costs 2013-11-23 03:45:47 +00:00
drdev
b4ba84a0d1 Fix Flash of Insight cost formatting and display {X} before generic cost 2013-11-23 03:29:14 +00:00
drdev
38074a1660 Fix so Card Detail updated when card script saved 2013-11-23 03:11:11 +00:00
drdev
ca5e20b1a9 Fix so rules not replaced when updated 2013-11-23 03:05:28 +00:00
drdev
c4db463338 Support saving script changes again 2013-11-23 02:54:52 +00:00
Maxmtg
34c2cb604f carddb editor will detect cases when rules are changed without rename 2013-11-23 00:11:07 +00:00
Maxmtg
f058e38004 CardRules.reinitializeFromScript 2013-11-23 00:03:51 +00:00
drdev
b38a8b5830 Format flashback cost on Firecat Blitz 2013-11-22 23:46:30 +00:00
drdev
8e76829a27 Format mana costs for Madness, Recover, and Miracle on instants/sorceries 2013-11-22 23:42:38 +00:00
Hellfish
f3bf092898 *Added Quest Item - Charm of Vigor: Let's you play best of 5 games rather than 3. 2013-11-22 23:03:49 +00:00
drdev
4dd2b87538 Rename allScripts hash map 2013-11-22 22:47:24 +00:00
Maxmtg
4d6930e903 prioritize text files over whatever found in zip 2013-11-22 18:10:06 +00:00
Maxmtg
3fecd16efd Have cardStorageReader read both zip and the files folder. There remains a task to replace duplicate cards 2013-11-22 17:09:07 +00:00
Maxmtg
29a535c71a moved CardStorageReader to core project 2013-11-22 16:51:38 +00:00
Maxmtg
3fd72f35df use concurrent map 2013-11-22 16:36:07 +00:00
Maxmtg
bb3d637738 adjust dependencies for CardScriptInfo 2013-11-22 16:32:56 +00:00
jendave
02fe620360 update guava 2013-11-22 09:25:24 +00:00
Hellfish
ebe0e350ff *Constructed defaults to 2 players to match previous behaviour 2013-11-22 05:41:52 +00:00
Sol
6dbe638a59 - Merfolk Seer wasn't displaying mana symbols in trigger cost 2013-11-22 04:42:22 +00:00
drdev
31b5f4181a Support displaying scripts in Workshop again 2013-11-22 03:21:37 +00:00
drdev
72d0cfdbbc Whitespace cleanup 2013-11-22 02:23:53 +00:00
drdev
f934764290 Prevent forge.profile.properties being accidentally committed to SVN 2013-11-22 01:38:33 +00:00
drdev
e28f809a58 Remove forge.profile.properties from SVN 2013-11-22 01:34:41 +00:00
Maxmtg
a88dd00a0d interface to edit cards 2013-11-21 23:37:34 +00:00
Hellfish
5d9de47fd5 *Expanded Constructed to up to 8 players, any amount AI or Human, to match up with the Variant update. 2013-11-21 22:54:30 +00:00
Maxmtg
7add28ad7d propper delete 2013-11-21 22:54:15 +00:00
Maxmtg
17b97aca28 hide some readers, collections and simple enums as nested classes, 2013-11-21 22:53:20 +00:00
Maxmtg
86b422230e added module 'game' 2013-11-21 21:32:49 +00:00
Hellfish
0193671453 *Fixed extra Field/Command/Hand views sticking around when for instance playing a game with 2 players after playing a game with 8 players. 2013-11-21 21:27:27 +00:00
Maxmtg
c4be1b8697 inlined type casts 2013-11-21 21:27:27 +00:00
Maxmtg
52f8880627 minor rearrangements in util package 2013-11-21 21:23:04 +00:00
Hellfish
f537e8cf78 *Prettyfied my previous addition. 2013-11-21 21:10:41 +00:00
Maxmtg
482af12b67 deck generation moved to core 2013-11-21 20:59:18 +00:00
Hellfish
2f85d50778 *Added the ability to specify multiple human (hotseat, of course) players or all AI for all variants 2013-11-21 20:25:14 +00:00
Hellfish
b51663c130 *Undoing Subclipse's mess 2013-11-21 19:46:05 +00:00
Hellfish
7ebc54352e Initial import. 2013-11-21 19:40:06 +00:00
jendave
6703e62823 minor doc fixes 2013-11-21 16:54:34 +00:00
Hellfish
e860e9e1af *"Remove All Counters" in a way that interacts other systems. Fixes Chronozoa + AEther Snap 2013-11-21 07:04:22 +00:00
Sloth
de3303abc9 - More use of activateForCost function. 2013-11-20 22:51:22 +00:00
Hellfish
a0695c08cc *Remove Inbound Tokens at appropriate places if the etb of the same token was replaced. Fixes Chronozoa. 2013-11-20 20:05:02 +00:00
Chris
c5aeb0785e - Cleared out the changes.txt file, now ready for new material.
- Added new card names to changes.txt.
2013-11-20 15:05:11 +00:00
Sloth
21b376848f - More use of activateForCost function. 2013-11-20 14:55:33 +00:00
swordshine
f8fe7e41b4 - Added Liar's Pendulum 2013-11-20 13:00:06 +00:00
Maxmtg
22caa0a408 updated references to org.apache.commons.lang version 2.6 to use version 3 ('cause forge used to import both 2.6 and 3.x versions of the named artifact) 2013-11-20 08:47:06 +00:00
Maxmtg
4d3945f5a4 moved all items to core, decks were included too. 2013-11-20 08:34:37 +00:00
Maxmtg
0786012ed8 rm ColorChanger.java (unused)
mv2core BoosterGenerator and UnOpenedProduct
2013-11-19 22:54:04 +00:00
Maxmtg
6e9a316460 remove UI references from CardStorageReader 2013-11-19 21:53:01 +00:00
Maxmtg
fd7d0e1d99 cleaned CardStorageReader of experimental code 2013-11-19 20:56:59 +00:00
Maxmtg
65199c7a81 moved cardFace and rules reader to core 2013-11-19 20:38:51 +00:00
Sloth
851094c523 - Added a doTriggerAINoCost function to RepeatAI. 2013-11-19 13:15:01 +00:00
Maxmtg
9b31032016 Moved cardDb and most of static data to core project. Had to rollback some incompatible changes. Sorry, drdev! 2013-11-19 07:40:58 +00:00
Sloth
a7fa173e52 - Content downloader will now show the number of item skipped. 2013-11-18 22:11:42 +00:00
Sloth
de4d83372c - Added 2 precons by Erazmus. 2013-11-18 20:59:32 +00:00
Sloth
c7f24490f3 - Some more work on the activateForCost function. 2013-11-18 20:57:25 +00:00
drdev
fabc7a3a27 Support updating card rules and display when saving script changes 2013-11-18 14:35:19 +00:00
swordshine
e830527f74 - Fixed other cards with optional decisions in ChangeZone effect 2013-11-18 12:48:02 +00:00
swordshine
5fc5feb636 - Fixed Cloudstone Curio 2013-11-18 12:41:19 +00:00
drdev
384af0001c Fix cursor positioning after undoing delete 2013-11-17 21:44:02 +00:00
drdev
2abc9b3f7c Create FUndoManager to encapsulate making undo/redo logic smarter and lumping typing changes together 2013-11-17 21:38:27 +00:00
drdev
9242f3439c Create FTextEditor to encapsulate a plain text editor with undo/redo support 2013-11-17 21:17:14 +00:00
drdev
b1ba8f0aa2 Add File menu and support Ctrl+S to save card in Workshop 2013-11-17 20:36:44 +00:00
drdev
20b3775280 Prompt to save card when switching to another card or another screen 2013-11-17 17:57:55 +00:00
drdev
c2bb321e17 Optimize determination of card script text being dirty 2013-11-17 08:33:00 +00:00
drdev
b0160287ba Add Workshop screen 2013-11-17 08:02:40 +00:00
drdev
284e2257f3 Fix Cascade 2013-11-16 21:43:57 +00:00
drdev
ec06d07f35 Breakup PrecostDesc$ of certain Entwine cards so mana cost part in CostDesc$
Make "Choose one" syntax consistent
2013-11-16 20:11:41 +00:00
drdev
5eca8807d8 Format mana costs for Echo, Cumulative Upkeep, and Unearth 2013-11-16 19:25:43 +00:00
drdev
5b3ee63299 Ignore forge-*/target directories for SVN 2013-11-16 19:22:41 +00:00
drdev
a6ffa3ae36 Update card scripts with CostDesc$ so its formatted 2013-11-16 19:17:44 +00:00
Sol
e021e66387 - Fixed Spell Description of MorphDown 2013-11-16 16:02:23 +00:00
Sol
eb70985551 - Fix Viridian Joiner description 2013-11-16 01:23:00 +00:00
Chris
cd6dc5158e [maven-release-plugin] prepare for next development iteration 2013-11-15 14:14:27 +00:00
Chris
942f30557c [maven-release-plugin] prepare release forge-1.5.5 2013-11-15 14:14:16 +00:00
Chris
1486d61e65 - Preparing the changes.txt file for the next beta build and release. 2013-11-15 13:57:23 +00:00
drdev
eb082d2f49 Fix abilities that used "tap" 2013-11-15 12:57:47 +00:00
drdev
7e6e691771 Ensure top of card text visible when first viewed in Card Detail 2013-11-15 12:14:03 +00:00
drdev
8e4962ceb1 Eliminate Card Detail flicker 2013-11-15 12:11:31 +00:00
drdev
414d82bd37 Fix The Tabernacle at Pendrell Vale mana format 2013-11-15 11:38:31 +00:00
drdev
4091f1d1d9 Fix Fleshwriter reminder cost format 2013-11-15 11:36:58 +00:00
drdev
fe0f4ddf99 Format Ninjutsu mana cost in reminder text 2013-11-15 11:34:27 +00:00
drdev
b0845c5795 Commit commented out imperfect code used for parsing ability in quotes 2013-11-15 11:24:44 +00:00
drdev
aeddb37d9d Format ability costs in quotes 2013-11-15 11:22:51 +00:00
drdev
1ff1c35486 Remove bullet in CHANGES.txt that no longer applies 2013-11-15 09:48:11 +00:00
drdev
6e585a7afa For now, if card only has 1 activated ability, don't show menu even if cost is automatic and undoable 2013-11-15 09:47:24 +00:00
Sloth
e0df16ee17 - Fixed AI using Exhume. 2013-11-15 09:42:15 +00:00
drdev
ae5cb7a17a Make ability menu items consistent regardless of containing mana symbol icons and make them respect menu item height 2013-11-15 09:40:47 +00:00
Sloth
8d79ed527f - Fixed a bug in ChooseCardEffect. 2013-11-15 09:37:08 +00:00
Sloth
d4bc84c6bd - Fixed Stormfront Riders. 2013-11-15 09:11:52 +00:00
drdev
9743a7d1b7 Mention fixing WUBRG order in CHANGES.txt 2013-11-15 09:03:48 +00:00
drdev
e5cffc623a Support updating colored mana order, phyrexian and hybrid mana format, and mana production abilities with multiple choices 2013-11-15 06:59:23 +00:00
drdev
9c0a559bce Update hybrid costs in abilities 2013-11-15 06:56:52 +00:00
drdev
b794bc5ee0 Update card ability descriptions containing choice of dual mana production 2013-11-15 06:26:33 +00:00
drdev
55c71d77e1 Fix colored mana order for card scripts 2013-11-15 06:04:12 +00:00
drdev
6914e30443 Fix colored mana order for card scripts 2013-11-15 05:48:47 +00:00
drdev
1c52e8e38a Update card ability descriptions containing choice of mana production 2013-11-15 03:00:09 +00:00
drdev
c38f169d1d Add space between ability description mana cost and reminder text 2013-11-15 00:58:24 +00:00
drdev
8f4b8dbf88 Prevent trimming spaces 2013-11-15 00:51:34 +00:00
drdev
b68f28b621 Update earwig_squad.txt prowl cost 2013-11-15 00:51:01 +00:00
drdev
a23eb2f869 Support outputting results from parse utility 2013-11-15 00:45:49 +00:00
drdev
8dfbf2b3b6 Update more card ability descriptions 2013-11-15 00:14:31 +00:00
drdev
70b0fab66a Use try/catch block to avoid crash from bad pattern 2013-11-14 23:27:08 +00:00
drdev
082e2ff2cd Add parse command line argument to allow performing function on all parsed cards 2013-11-14 23:16:28 +00:00
Sloth
12af50d473 - Update more card ability descriptions. 2013-11-14 22:54:50 +00:00
Sloth
b1cde02dd4 - Update more card ability descriptions with {T} symbol. 2013-11-14 22:22:19 +00:00
jendave
2d737ab092 a little progress on the Mac app bundle 2013-11-14 21:59:44 +00:00
jendave
8cd9220f36 update release plugin. Update site urls for new maven structure. 2013-11-14 17:08:54 +00:00
swordshine
838d4a98aa - Update more scripts 2013-11-14 13:50:40 +00:00
swordshine
d32bfea94e - Fixed Braid of Fire 2013-11-14 13:25:28 +00:00
swordshine
181a7f5b9c - Updated basic land abilities to display mana symbol icons 2013-11-14 10:57:51 +00:00
drdev
ee00b544f7 Support displaying Chaos icon in details 2013-11-14 09:20:29 +00:00
drdev
5548c71bda Update more card ability descriptions 2013-11-14 08:46:01 +00:00
drdev
ae7a6b79b7 Update more card ability descriptions 2013-11-14 08:27:50 +00:00
drdev
96d2338bea Update card ability descriptions so mana symbols are displayed as icons 2013-11-14 07:48:31 +00:00
drdev
21217e030b Code cleanup 2013-11-14 04:42:59 +00:00
drdev
1d63f4b4a1 Update CHANGES.txt 2013-11-14 04:19:34 +00:00
drdev
8843e5bfe7 Properly translate only mana costs to icons
Make snow and certain hybrid mana symbols display correctly
Show mana symbols in game prompt
2013-11-14 04:08:24 +00:00
Sloth
48b97f8632 - Fixed Balduvian Frostwaker. 2013-11-13 22:51:29 +00:00
jendave
20184fb4c0 ignore IntelliJ files 2013-11-12 08:46:35 +00:00
drdev
76000a90fb Update CHANGES.txt 2013-11-12 07:08:52 +00:00
drdev
4aebd350ee Support showing mana symbols in ability menu and card detail pane 2013-11-12 07:06:09 +00:00
Sol
13f4a0abb4 - Adding Enchant opponent line to Psychic Posssesion 2013-11-12 02:19:51 +00:00
drdev
35ea297090 Make target arrows appear in correct place in window mode 2013-11-12 02:15:55 +00:00
drdev
f11909d6da Prevent repeating shortcuts for abilities 2013-11-12 01:57:24 +00:00
jendave
5df82652b6 clean ups 2013-11-11 16:07:37 +00:00
Chris
8fde8ebe5f - Added new card names to changes.txt. 2013-11-11 13:43:52 +00:00
drdev
6bc20a8830 Update CHANGES.txt 2013-11-11 12:48:18 +00:00
drdev
0bbd9c103a Make menu items easier to click
Mention context menu ability select in CHANGES.txt
2013-11-11 12:39:18 +00:00
jendave
0b68e2ade6 re-org 2013-11-11 08:27:14 +00:00
jendave
1ca79539b6 update deps and re-org a few things 2013-11-11 08:27:00 +00:00
drdev
1c2c3d6d51 Ensure first ability selected in menu by default 2013-11-11 02:55:18 +00:00
drdev
bdb1243409 Prevent showing menu if no ability can be played 2013-11-11 02:36:44 +00:00
drdev
812d4ca519 Prevent returning ability that can't be played 2013-11-11 02:22:27 +00:00
swordshine
4e7e14cbf3 - Added Invasion Plans 2013-11-11 00:29:43 +00:00
swordshine
2064b6517e - Temporarily removed Arboria
- Fixed Quicksilver Dragon
2013-11-11 00:22:11 +00:00
drdev
c9f491a483 Show unplayable activated abilities disabled unless activator or zone restriction
Prompt for single activated ability unless mana ability
Show mana abilities before other abilities to match card order
2013-11-10 21:48:55 +00:00
Maxmtg
be2ec80f0c redirect dependency between Card and IPaperCard 2013-11-10 21:37:07 +00:00
drdev
1c13e8dc10 Show context menu to select ability instead of dialog 2013-11-10 20:25:25 +00:00
drdev
4748aa2262 Fix typo in function name 2013-11-10 18:56:41 +00:00
swordshine
a3376b0502 - Added Arboria, Premature Burial, and Sacred Ground 2013-11-10 05:14:26 +00:00
swordshine
c073d39cd6 - Updated scripts 2013-11-10 04:51:16 +00:00
Chris
b508f78f04 Moved the CHANGES.txt, LICENSE.txt, forge.profile.properties.example and README.txt files to forge-gui folder. 2013-11-09 15:33:38 +00:00
Chris
8424da8311 - Added new card names to changes.txt. 2013-11-09 15:17:05 +00:00
swordshine
cfd9d55668 - Added Quicksilver Dragon 2013-11-09 10:01:48 +00:00
swordshine
417e3bb104 - Added Reflecting Mirror 2013-11-09 09:36:45 +00:00
swordshine
320d4f62d9 2013-11-09 08:24:47 +00:00
Maxmtg
6c712d86fb moved a few classes to core module 2013-11-09 02:27:38 +00:00
jendave
a0479f9610 Test run of moving files to modules. 2013-11-08 23:21:11 +00:00
Maxmtg
374a744a44 added java nature to forge-gui project, set up classpath (copied it from former project) 2013-11-08 21:57:20 +00:00
Maxmtg
81416d49de added reference from core to guava, set runtime version of ai and core to 1.7 2013-11-08 21:35:14 +00:00
Sloth
31ebd62c83 - Added more precons by Erazmus. 2013-11-08 20:18:39 +00:00
jendave
93dd4af6ed fix eclipse issues 2013-11-08 18:39:59 +00:00
jendave
9f0308dd66 Start of re-org into modules 2013-11-08 10:05:52 +00:00
Sloth
f9298703d4 - Added some precons by Erazmus. 2013-11-08 08:52:31 +00:00
Sloth
3992032e8e - Added Rebound. 2013-11-07 23:08:44 +00:00
Sloth
58dacc0789 - Added Silver Wyvern. 2013-11-07 22:54:17 +00:00
Chris
b3280923d9 - Added new card names to changes.txt. 2013-11-07 14:49:02 +00:00
Sloth
e83da9ff9c - Some safety fixes for Muck Drubb. 2013-11-07 09:17:48 +00:00
Sloth
8b1d0fc759 - Added Muck Drubb. 2013-11-06 22:45:35 +00:00
Sloth
ff869933be - Cleanup. 2013-11-06 22:15:51 +00:00
Sloth
fb0af4dc18 - Start of using isValid function for SpellAbilities. 2013-11-06 19:39:34 +00:00
Sloth
6f5551d78d - Fixed Razia's Purification. 2013-11-05 22:32:40 +00:00
swordshine
a90efe4939 - Fixed Mirror Strike 2013-11-05 02:37:33 +00:00
Sloth
46c37b9d82 - Updated mtg-data.txt.
- Added Oracle texts to C13 cards.
2013-11-04 21:10:55 +00:00
drdev
3e23eb517b Use FMouseAdapter to make catalog and deck tables more responsive 2013-11-04 03:17:28 +00:00
Sloth
9cf75ea844 - Script fixes and updates. 2013-11-03 22:02:28 +00:00
drdev
5565b4a4d0 Retain column sort direction between sessions 2013-11-03 18:44:14 +00:00
drdev
bde5967ef4 Remember column order in Deck Editor 2013-11-03 17:47:57 +00:00
Chris
f3560238fe - Added new card names to changes.txt. 2013-11-03 13:52:06 +00:00
swordshine
f97f489aba - Added Juxtapose 2013-11-03 13:28:12 +00:00
swordshine
454b7715c0 - Added Scheme: Your Inescapable Doom 2013-11-03 11:46:46 +00:00
swordshine
ac1e279235 - Fixed LibraryPosition 2013-11-03 07:50:02 +00:00
swordshine
5950162663 - Added Johan
- Updated mtgdata
2013-11-03 07:32:08 +00:00
Chris
6a52b46192 - Added a fluff piece to the changes.txt file. 2013-11-02 12:42:27 +00:00
Sloth
f5558eec7f - Updated and fixed some C13 cards. 2013-11-02 07:37:33 +00:00
Chris
0c39f596a3 - Cleared out the changes.txt file, now ready for new material. 2013-11-01 23:09:20 +00:00
Sloth
ba3c544484 - Fixed Saltskitter. 2013-11-01 19:42:54 +00:00
jendave
e0063727a9 update to latest deps and plugins. Did not update to latest jetty and guava since they may cause issues 2013-11-01 16:27:50 +00:00
swordshine
7b68821a8c - Merged C13 Branch
- Fixed Curse of Chaos, Curse of the Forsaken, Fell Shepherd
2013-11-01 14:19:28 +00:00
Chris
4a84b408d4 [maven-release-plugin] prepare for next development iteration 2013-11-01 13:06:01 +00:00
Chris
b9aec4ffb8 [maven-release-plugin] prepare release forge-1.5.4 2013-11-01 13:05:50 +00:00
Chris
1a958e758f - Preparing the changes.txt file for the next beta build and release. 2013-11-01 12:55:46 +00:00
RumbleBBU
74527319aa Relabeled the starting pool color distribution options to make them more intuitive. Merged the randomization option to the pulldown menu. 2013-10-31 09:48:35 +00:00
drdev
a66e7ad14f Add click effect to opague and selectable FLabels to make them feel more like buttons 2013-10-30 14:40:36 +00:00
drdev
e4b91416dd Update DeckLister to use FMouseAdapter 2013-10-30 14:00:37 +00:00
drdev
7735c705c0 Fix so right-clicking stat label that's the only selected one in group will re-select all other labels in group 2013-10-30 13:15:56 +00:00
drdev
31ab48d0d4 Let FMouseAdapter raise click if mouse moved outside component then back inside before releasing mouse 2013-10-30 13:02:44 +00:00
drdev
e5cf9da31d Update FLabel to use new FMouseAdapter 2013-10-30 12:39:10 +00:00
drdev
9eac64ab7b Create FMouseAdapter to facilitate more reliable and responsive click handling
Improve responsiveness of table sorting in ItemManagers
Prevent events on editor tables being duplicated, resulting in poor performance and inverting sort sometimes not working
2013-10-30 12:29:30 +00:00
swordshine
7131cebf7d - Fixed last commit 2013-10-30 04:49:55 +00:00
swordshine
bfb4a5ee57 - Commander 2013 edition file 2013-10-30 03:04:38 +00:00
drdev
21e250bf36 Prevent auto-inverting sort direction when switching away from editor and back 2013-10-28 14:04:31 +00:00
swordshine
f5b1ac970b - Fixed Scroll Thief and Stealer of Secrets 2013-10-28 03:04:36 +00:00
drdev
c85f14f27c Fix crash when taking over another player's turn 2013-10-27 19:14:00 +00:00
drdev
c0cf6198a4 Mention addition of concede confirmation in CHANGES.txt 2013-10-27 06:53:39 +00:00
drdev
4d3012fee7 Ensure Match screen active before showing concede prompt 2013-10-27 06:52:17 +00:00
drdev
75726dc272 Make user confirm that they want to concede the current game 2013-10-27 06:47:20 +00:00
drdev
260724af80 Fix so Match screen uses the correct controller (so correct menus built for example) 2013-10-27 06:33:26 +00:00
drdev
2b37cf217f Updated CHANGES.txt for combo box visual tweaks 2013-10-27 06:23:03 +00:00
drdev
669ff579c5 Fix so combo boxes on Constructed home screen update when skin switched 2013-10-27 06:17:23 +00:00
drdev
d0e65fe1bc Use FComboBox in place of JComboBox 2013-10-27 05:54:22 +00:00
drdev
4485e46801 Remove unused FComboBox properties 2013-10-27 05:27:51 +00:00
drdev
2265687c75 Make combo box be all one piece 2013-10-27 05:12:57 +00:00
drdev
f70523bf14 Update CHANGES.txt for Draft fixes 2013-10-27 03:56:13 +00:00
drdev
48013b9339 Fix a couple more issues with drafting 2013-10-27 03:46:20 +00:00
drdev
e9e1caf9d1 Prevent losing draft picks when switching away and back
Prevent canceling naming draft pool which caused a crash
2013-10-27 03:29:20 +00:00
drdev
5b17e2beda Make it so Draft screen closes when draft process finished and Draft Deck Editor immediately opens 2013-10-27 03:17:45 +00:00
drdev
19bc74f88d Fix a few remaining places that would show prompts outside main window 2013-10-27 02:52:14 +00:00
drdev
ae0069652a Fix switching between Editors 2013-10-26 21:49:26 +00:00
Sloth
5ec7f01b7a - Added the new quest deck Robot Santa 3. 2013-10-26 20:55:39 +00:00
Sloth
691f798f90 - Replaced the interface ITargetable with the class GameObject (which reflects the term used in the rules for players, cards and spell/abilities).
- Preparations for extending isValid and hasProperty functions for spell/abilities.
2013-10-25 21:41:38 +00:00
Chris
d5bef0861b - Added new card names to changes.txt. 2013-10-24 13:27:07 +00:00
swordshine
87dd938cde - Added Graxiplon 2013-10-24 08:54:27 +00:00
Sloth
c4e0d3e7ce - Added Glarecaster. 2013-10-23 21:52:06 +00:00
Sloth
c705722bdc - Updated some AI SVars. 2013-10-23 15:53:08 +00:00
Sloth
6424fa151a - Added a hard version of the Leela quest deck. 2013-10-23 14:04:44 +00:00
swordshine
0646e49359 - Added Scheme: Only Blood Ends Your Nightmares
- Added Cyclopean Tomb
2013-10-23 00:24:38 +00:00
Sloth
6ddae9e9da - Updated some Shandalar world quest decks. 2013-10-22 20:34:41 +00:00
Sloth
efc7be1637 - Fixed face-down cards moving to Hand or Library not being turned face up. 2013-10-22 13:41:16 +00:00
Chris
1de80be872 - Added new card names to changes.txt. 2013-10-22 13:02:54 +00:00
swordshine
f8740f1268 - Updated scripts (Psychogenic Probe should trigger only once for these cards) 2013-10-22 12:19:08 +00:00
swordshine
d6f30c9220 - Added Dichotomancy 2013-10-22 12:02:28 +00:00
swordshine
f2d7004b02 - C13: Added From the Ashes 2013-10-22 11:08:14 +00:00
swordshine
6ecf800bab - Added Seeds of Innocence 2013-10-22 10:23:24 +00:00
Sloth
6f92a834da - Some deck updates. 2013-10-21 21:06:56 +00:00
Sloth
a7a85d22d5 - Added some card specific AI for Serene Master and Shape Stealer. 2013-10-21 20:18:05 +00:00
Sloth
178024e248 - Fixed AI freeze caused by Fireball. 2013-10-21 19:53:04 +00:00
swordshine
798e93af46 - Fixed Opal Palace (using an etbCounter replacement now) 2013-10-21 13:40:36 +00:00
swordshine
3cb125ae4a - C13: Added Opal Palace 2013-10-21 12:12:01 +00:00
swordshine
30aed49e87 - C13: Added Serene Master (please improve AiAttackController.declareAttackers and AiBlockController for this card) 2013-10-21 06:27:48 +00:00
swordshine
d0173a2341 - Fixed r23523 LibraryPosition 2013-10-21 02:56:07 +00:00
drdev
fb7a61ab30 Fix crash when purchasing items in the Bazaar 2013-10-20 23:07:37 +00:00
Sloth
13e09c73bc - Fixed text of Force of Nature. 2013-10-20 07:34:25 +00:00
swordshine
6fdaf4fda4 - C13: Added Tempt with Glory, Tempt with Reflections and Unexpectedly Absent 2013-10-20 05:24:56 +00:00
Chris
ba79089bed - Cleared out the changes.txt file, now ready for new material.
- Added new card names to changes.txt.
2013-10-19 16:22:03 +00:00
Sloth
a8670467be - Fixed Tangle Wire. 2013-10-19 11:26:31 +00:00
swordshine
4dd84cdb2e - C13: Added Naya Soulbeast and Tempt with Discovery 2013-10-19 04:09:38 +00:00
Sol
028d6c0a1f - Using CARDNAME instead of Seasinger for ability stealing cards 2013-10-19 03:36:39 +00:00
swordshine
ca2584f598 - C13: Added Sudden Demise, Tempt with Vengeance, Terra Ravager, Toxic Deluge, Widespread Panic and Witch Hunt 2013-10-19 00:49:33 +00:00
Sol
17e96250f7 - Fix issue with a few Avatars that spawn cards outside of the game not knowing what game they are apart of 2013-10-18 21:48:38 +00:00
Chris
79fdd21817 [maven-release-plugin] prepare for next development iteration 2013-10-18 14:33:15 +00:00
Chris
a0daa7c4be [maven-release-plugin] prepare release forge-1.5.3 2013-10-18 14:33:05 +00:00
Chris
000d0d3119 - Preparing the changes.txt file for the next beta build and release. 2013-10-18 14:21:49 +00:00
Chris
3b01b4f3dd - Added new card names to changes.txt. 2013-10-18 12:54:11 +00:00
swordshine
f0613e9660 - C13: Added Act of Authority and Angel of Finality 2013-10-18 05:43:51 +00:00
Sloth
1c51ed37c2 - Fixed an issue with the "May be played by your opponent" keyword. 2013-10-17 21:18:24 +00:00
Chris
2bfcd92da9 - Added new card names to changes.txt. 2013-10-17 12:50:02 +00:00
RumbleBBU
0bf676448b Fixed a bug that caused the Quest starting pool reduction incorrectly affect randomized starting pools. 2013-10-17 12:43:46 +00:00
RumbleBBU
5bceff414e Bias steepness now reduces the total amount of cards in your Quest starting pool. Effect of steepness slightly increased to compensate for this in your chosen color.
Slightly tweaked and relabeled the UI components for color preference selection, to make them slightly more intuitive.
2013-10-17 10:11:31 +00:00
swordshine
1b65231e30 - C13: Added Illusionist's Gambit and True-Name Nemesis 2013-10-17 05:23:38 +00:00
drdev
8b39b69bb7 Move/rename FControl.Screens to FScreen and make that define the Navigation tab data instead of INavigationTabData
Make it so each Deck Editor type appears in its own tab
Make Bazaar screen act more like other screens
2013-10-17 04:23:48 +00:00
Chris
4dfab45544 - Added new card names to changes.txt. 2013-10-16 12:53:43 +00:00
swordshine
f046dbf3cc - C13: Added Surveyor's Scope 2013-10-16 06:34:58 +00:00
swordshine
2e5f9350eb - Replicate is a trigger now 2013-10-16 00:35:36 +00:00
Sloth
938784d6e8 - Fixed Kiki-Jiki, Mirror Breaker not granting haste to transformed cards. 2013-10-15 13:59:43 +00:00
Chris
02c582a695 - Added new card names to changes.txt. 2013-10-15 13:05:01 +00:00
swordshine
2851d49c3b - C13: Added Eye of Doom 2013-10-15 12:15:33 +00:00
swordshine
e88af1f6ba - Converted Madness 2013-10-15 10:48:32 +00:00
swordshine
da3b6bf19e - Converted Persist/Undying to script (these triggers can be stifled and copied now) 2013-10-15 00:21:23 +00:00
Chris
da28e10ac7 - Added new card names to changes.txt. 2013-10-14 13:42:37 +00:00
swordshine
e6b260cc60 - C13: Added Curse of Chaos 2013-10-14 04:12:40 +00:00
Sloth
2cea9debcf - Added Carom. 2013-10-13 19:26:17 +00:00
swordshine
768a26a5d4 - Converted Tangle Wire to script 2013-10-13 08:33:38 +00:00
swordshine
0e251ffb6a - Converted Drop of Honey and Porphyry Nodes to script 2013-10-13 08:00:44 +00:00
swordshine
74c04ea561 - Cleanup 2013-10-13 07:20:08 +00:00
swordshine
e92cc2735b - Converted Braid of Fire to script 2013-10-13 07:15:59 +00:00
swordshine
6a94535b78 - Converted Intruder Alarm, Smoke and Stoic Angel to script 2013-10-13 05:24:17 +00:00
swordshine
17ad490f97 - Converted Damping Field and Imi Statue to script 2013-10-13 05:07:07 +00:00
swordshine
704e4c4ef7 - Converted Winter Orb and Mungha Wurm to script 2013-10-13 04:57:08 +00:00
Chris
36a301275e - Added new card names to changes.txt. 2013-10-12 20:20:15 +00:00
drdev
bb2a01512e Make disabled close buttons appear disabled
Fix contrast skin colors
2013-10-12 20:17:52 +00:00
Sloth
ad73c9391d - Added the WU Heroic deck Bilbo Baggins 2 with help from Nordos. 2013-10-12 17:13:07 +00:00
swordshine
ce33d146d0 - Converted Land Equilibrium to script 2013-10-12 13:06:37 +00:00
Sloth
055695890e - Cleanup. 2013-10-12 13:02:12 +00:00
swordshine
028579eed4 - Added Protective Sphere 2013-10-12 11:20:54 +00:00
swordshine
5c836228f7 - Added Spellweaver Volute 2013-10-12 05:48:07 +00:00
swordshine
0663eee49a - Added Takklemaggot 2013-10-12 02:38:30 +00:00
dripton
4d9205699b Fix typo in comment 2013-10-11 15:12:52 +00:00
drdev
d8a5993a28 Ensure close buttons disabled along with navigation tabs 2013-10-11 06:11:31 +00:00
drdev
6e7e19fd34 Disable navigation tabs while overlay open 2013-10-11 05:56:22 +00:00
drdev
9e55ee9be1 Improve animation of navigation bar reveal and prevent it being stuck open with auto-hiding 2013-10-11 05:41:06 +00:00
drdev
ae93665645 Fix so remaining dialogs show up on top of Forge, such as sideboard 2013-10-11 01:52:50 +00:00
drdev
7eb84bb6a3 Update tooltip for X button based on current screen and close action 2013-10-11 01:18:39 +00:00
drdev
3006cc9431 Ensure previous game ended when continuing Guantlet 2013-10-11 00:32:22 +00:00
Sloth
480629953b - Fixed Ghoulcaller's Chant. 2013-10-10 21:59:30 +00:00
dripton
c54ed09d38 Add try/catch to hiddenOriginCanPlayAi
If a SpellAbility's origin is something like "Graveyard,Library",
ZoneType.smartValueOf throws an IllegalArgumentException.  Catch
this exception and return false.  This prevents the crash, but
means that the AI still can't play cards like Doomsday.

Fixes bug 745
2013-10-10 14:29:53 +00:00
drdev
a098b56339 Fix typos 2013-10-10 03:19:28 +00:00
drdev
7a9f8d9788 Add setting to control behavior of X button in upper right (Close Screen vs. Exit Forge) 2013-10-10 03:06:05 +00:00
Sloth
7bd822b96a - Fixed human player not being able to activate mana abilities as a spell/ability is played when Mana produced replacement effects are around. 2013-10-09 13:57:59 +00:00
swordshine
d20e9d5629 - Fixed Dark Betrayal 2013-10-09 10:34:20 +00:00
RumbleBBU
9b61a99dc3 Added a preference to adjust color bias steepness when using color-biased Quest starting pools. 2013-10-09 09:49:38 +00:00
drdev
4c1f670147 Set dialog default to Yes when exiting Forge without game active
Ensure Deck Editor visible if and when "Save Changes?" dialog would appear
2013-10-09 05:10:59 +00:00
drdev
c05d299fe4 Always prompt user before exiting or restarting Forge 2013-10-09 04:45:44 +00:00
drdev
0ba8b530fe Fix so quitting Quest games works with tabs
Prevent adding multiple game tabs
2013-10-09 04:33:45 +00:00
drdev
0a6f7afeff Add navigation tab for Bazaar
Prevent arrow button remaining visible when switching to Bazaar from Home screen
2013-10-09 01:56:03 +00:00
Sloth
483f274bac - Fixed my commit r23388. 2013-10-08 21:15:03 +00:00
Chris
1fd36a4623 - Added new card names to changes.txt. 2013-10-08 13:55:52 +00:00
drdev
3b1ab80c1c Add navigation tabs for switching between Home, Deck Editor, and Match screens
Remove Deck Editor and Exit Forge buttons home screen
Exit Forge when X button in upper right clicked
Add safety checks before exit or restart
Show warning if you try to Start a game while one is already active (at least for now)
2013-10-08 13:07:33 +00:00
RumbleBBU
abe0cec346 - Added an experimental UI that allows you to choose a preferred color in new Quest games. Also added a "Balanced color distribution" checkbox that can be unchecked to completely randomize the color distribution in new Quest games.
- Minor checkstyle fixes to old issues in VSubmenuQuestData and CSubmenuQuestData.
2013-10-08 12:32:42 +00:00
spr
6e1e22d6cb - Refactor GamePlayerUtil. 2013-10-08 10:04:32 +00:00
drdev
7650ec9323 Minimize if switching from Full Screen Forge to outside application window 2013-10-08 09:20:47 +00:00
spr
fe3fedaf64 - Renamed "Themed ComboBox" setting to "Enable Themes" to better reflect its purpose. 2013-10-08 07:16:52 +00:00
spr
f21f187890 - Removed redundant Visual Themes section from Game Settings -> Preferences. 2013-10-08 07:10:59 +00:00
spr
41e164d131 - Removed redundant Card Overlay Options section from Game Settings -> Preferences. 2013-10-08 07:04:28 +00:00
drdev
adb6a0b7f0 Fix so Deck Importer and other JDialogs display centered over main Forge window 2013-10-08 02:11:50 +00:00
drdev
31618c1611 Mention dialog fix in CHANGES.txt 2013-10-08 01:25:28 +00:00
drdev
6eba60638b Prevent Full Screen Forge being minimized when deactivated (such as by opening JOptionPane dialog) 2013-10-08 01:15:29 +00:00
drdev
e4040d4a47 Fix so JOptionPane dialogs always display at center of main window by default 2013-10-08 00:43:34 +00:00
drdev
c53cd32291 Fix so, when maximized, Forge doesn't extend below top of taskbar if screen has a top inset 2013-10-07 23:07:42 +00:00
drdev
172ca340ae Disable Forge button and Forge menu shortcut keys while overlay open 2013-10-07 22:53:14 +00:00
drdev
a8451abea4 Delay hiding titlebar a half second after becoming full-screen 2013-10-07 22:26:02 +00:00
drdev
cdf67a3865 Mention fix to Mac minimize crash 2013-10-07 22:05:31 +00:00
Sloth
e2f2fc4804 - Added a medium version of the Hugo Drax deck. 2013-10-07 20:41:08 +00:00
spr
db9a451e32 - Added option to replace default "Human" with custom name during gameplay. (http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=11553). 2013-10-07 19:56:00 +00:00
spr
a8cf682f71 - Added very simple About box to display Forge version. 2013-10-07 19:46:05 +00:00
spr
0c721f1776 - FNavBar tweak - Revealspeed = 200, revealDelay = 100. 2013-10-07 19:39:48 +00:00
Chris
f8194aac7a - Added new card names to changes.txt. 2013-10-07 13:14:57 +00:00
swordshine
15dd60ff3e - Update the script for Chained to the Rocks 2013-10-07 12:45:55 +00:00
drdev
d54441a9b2 Update CHANGES.txt for Full Screen support 2013-10-07 06:59:19 +00:00
drdev
4e75bfb4d0 Remove status bar, instead using tooltips for menu hints
Prevent overlays showing on top of titlebar
2013-10-07 06:52:41 +00:00
drdev
3e350d03c6 If titlebar unlocked or window made full-screen, delay hiding titlebar until mouse moves away 2013-10-07 05:42:10 +00:00
drdev
2fa44e1ae2 Always show Full Screen button right of Minimize button 2013-10-07 05:23:43 +00:00
drdev
1e5244cee0 Only allow titlebar being hidden when in Full Screen mode
Ensure Full Screen window appears on correct monitor
Add buttons to toggle Full Screen and lock/unlock titlebar
Make clock appear in titlebar when Full Screen and hiding status bar
Prevent moving window or double-click restore down when Full Screen
2013-10-07 05:17:27 +00:00
swordshine
6043c2738d - Added Snowblind 2013-10-07 05:09:55 +00:00
drdev
013cf5eb13 Disable Direct3D to improve rendering performance 2013-10-07 02:37:03 +00:00
drdev
a413fe2dc1 Ensure full-screen still works after using Set Window Size when in in Full Screen mode 2013-10-06 16:14:02 +00:00
drdev
a2bf02eb82 Support toggling Full Screen with F11 on all platforms
Fix way window state is updated (minimized/normal/maximized/full screen)
2013-10-06 16:08:29 +00:00
swordshine
229485494a - Update scripts 2013-10-06 11:40:05 +00:00
Hellfish
4ea3439667 *Updated Kederekt Parasite for multiplayer 2013-10-06 09:07:17 +00:00
Sloth
4154d31143 - Fixed possible NPE caused by Haunt. 2013-10-06 08:44:18 +00:00
Hellfish
30b660f802 *Missed a vital piece of the AI commander pay fail fix. 2013-10-06 08:08:39 +00:00
drdev
b79cf45778 Prevent Forge covering up taskbar when maximized 2013-10-05 22:53:47 +00:00
drdev
c6e9cbd69b Skin tooltips 2013-10-05 22:12:51 +00:00
drdev
7e493ed129 Change constants to static 2013-10-05 21:53:40 +00:00
drdev
c3ed6ebe79 Make reveal speed easier to tweak 2013-10-05 21:47:32 +00:00
Hellfish
27e33ec1b1 *Fixed AI failing to pay for recasting commander. 2013-10-05 18:02:42 +00:00
Chris
7601d5a2bd - Cleared out the changes.txt file, now ready for new material.
- Added new card names to changes.txt.
2013-10-05 13:24:12 +00:00
swordshine
a9fb42eb15 - Added Excavator 2013-10-05 11:38:53 +00:00
Sloth
cd9cdbf1be - Added AEtherplasm. 2013-10-05 11:38:33 +00:00
swordshine
e68a716587 - Added Elemental Resonance 2013-10-05 06:49:09 +00:00
swordshine
bab5672669 - Added Charmed Pendant 2013-10-05 06:31:54 +00:00
drdev
80f083e141 Increase speed and responsiveness of titlebar reveal 2013-10-05 00:23:08 +00:00
Sloth
91d37b38cc - Fixed Ghazban Ogre. 2013-10-04 19:55:21 +00:00
Chris
9349fa9217 [maven-release-plugin] prepare for next development iteration 2013-10-04 16:25:23 +00:00
Chris
748523f9cf [maven-release-plugin] prepare release forge-1.5.2 2013-10-04 16:25:13 +00:00
Chris
e64336abe9 - Preparing the changes.txt file for the next beta build and release. 2013-10-04 16:14:56 +00:00
RumbleBBU
15450a7fba - Added supporting infrastructure for manipulating the initial card distribution in new Quest games. The user interface components that actually enable this feature will be added after the next beta.
- Lots of checkstyle fixes to old issues.
2013-10-04 11:52:56 +00:00
drdev
ad2f63cd8f Fix so Forge menu items update from skin change properly 2013-10-04 06:43:41 +00:00
drdev
dbd3d956fb Avoid losing hidden title bar setting when switching to window mode and back to full screen 2013-10-04 06:12:15 +00:00
drdev
aabb4399ed Support temporarily revealing hidden title bar by moving mouse to top of screen 2013-10-04 05:27:18 +00:00
drdev
a99386ac1d Show clock in titlebar if maximized and status bar hidden 2013-10-04 02:43:14 +00:00
Sloth
b400f22d15 - Fixed description of Nest Invader. 2013-10-03 18:12:03 +00:00
moomarc
8843054b89 - New skin added 2013-10-03 16:34:14 +00:00
Sloth
8c8bde0799 - Fixed cost lists not using LKI's (once again). 2013-10-03 11:23:21 +00:00
drdev
109a990b28 Dim status bar text color and fix padding right of clock 2013-10-03 09:26:31 +00:00
drdev
8e9ac2e3e7 Make status bar text look better aligned 2013-10-03 09:05:44 +00:00
drdev
c0ad9b1b71 Update CHANGES.txt for Forge button changes 2013-10-03 08:37:11 +00:00
Maxmtg
01609a1f29 adjusted visibility of listInSync, removed unused import, removed test that does not test draft rankings anyway 2013-10-03 08:26:30 +00:00
drdev
2f5219615d Create Forge button which, when clicked, displays popup menu containing items from old menu bar
Support hiding status bar (F12) and saving title bar and status bar visibility between sessions
Changed F1 to be a shortcut for launching the Forge wiki
2013-10-03 08:26:11 +00:00
spr
91cd93783f - Fix: Constructed home screen "Game" menu was not being cleared from MenuBar when switching to other home screens; Menubar was not being repainted properly; 2013-10-03 08:17:44 +00:00
Maxmtg
2d8e4e9053 removed some (not all) slowdonws in quest card shop when owner of a big collection purchases a fatpack. (there were N redraws of owned card list instead of just one) 2013-10-03 08:06:10 +00:00
swordshine
9500b3a5ae - Updated scripts 2013-10-03 07:37:00 +00:00
swordshine
84e124bbf5 - Updated Cube by Juzamjedi 2013-10-03 07:28:58 +00:00
drdev
0d0377ae4a Remove clock from titlebar 2013-10-03 02:14:25 +00:00
drdev
9e1ec67253 Move version and clock to new status bar 2013-10-03 02:11:03 +00:00
drdev
4f8ee5ea52 Increase height of titlebar and size of minimize, maximize, and close icons
Fix so minimize, maximize, and close buttons allow clicking very top of screen and allow canceling click by moving mouse away before releasing
2013-10-03 00:50:31 +00:00
drdev
56654ffcb1 Cache time formatter 2013-10-03 00:28:55 +00:00
Chris
ac48d3634b fixed 2 broken pic URLs. 2013-10-02 13:45:03 +00:00
spr
934e4cbb2c - Constructed game now remembers the last deck played (both left and right decks) and restores at next startup.
- Default human deck type is Preconstructed decks.
2013-10-02 13:35:09 +00:00
Chris
53e631d65f - Cleared out the changes.txt file, now ready for new material. 2013-10-02 12:47:46 +00:00
swordshine
1483d7c969 - update some SVars 2013-10-02 08:29:48 +00:00
spr
71ddee1033 - Constructed screen updates based on feedback. 2013-10-02 06:54:59 +00:00
Maxmtg
480bba7a8c making multimaps in combat class synchronized 2013-10-01 20:40:28 +00:00
Chris
9c4c529c62 [maven-release-plugin] prepare for next development iteration 2013-10-01 14:22:19 +00:00
Chris
2df834804e [maven-release-plugin] prepare release forge-1.5.1 2013-10-01 14:22:10 +00:00
Chris
fb5fab037e - Added new card names to changes.txt.
- Preparing the changes.txt file for the next beta build and release.
2013-10-01 14:09:08 +00:00
spr
fc2e170d09 - Titlebar clock should now display time using format determined by system locale. 2013-10-01 06:32:38 +00:00
swordshine
77602a2aa2 - Fixed NPE for Dev mode 2013-10-01 01:57:52 +00:00
swordshine
dd668cb0fe - Added Reality Twist and Ritual of Subdual 2013-10-01 01:50:32 +00:00
spr
2a7b1f3c68 - Deck category radio buttons replaced by combo; transparent deck list. 2013-09-30 21:38:16 +00:00
spr
05b4a064ab - moved FDeckChooser to new folder. 2013-09-30 21:29:51 +00:00
Sloth
e6f64aebd1 - Cleanup. 2013-09-30 18:25:59 +00:00
spr
e2d9006e5c - Constructed game launch screen revamp (phase 1); Busy indicator added to theme switch menu. 2013-09-30 08:00:52 +00:00
Maxmtg
d991861421 DeckImport - applied skin and enabled filter to import cards 'not older than' chosen date 2013-09-30 06:51:08 +00:00
Maxmtg
c04d43ac66 + methods to get latest card by a certain date 2013-09-30 06:00:57 +00:00
Maxmtg
9cd7e8e034 combat event attackers declared - used google multimap 2013-09-30 03:17:05 +00:00
Maxmtg
ee198870f7 carddb - use google Multimap 2013-09-30 03:16:47 +00:00
Maxmtg
5138ba7605 correct file for FTV:20 2013-09-30 02:59:55 +00:00
dripton
426fab34bc Ugly workaround for IndexOutOfBoundsException when one thread
reads hiddenExtrinsicKeyword while another thread modifies it.

Fixes bug reported in 
http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=6333&start=2445#p133874
2013-09-29 20:44:59 +00:00
drdev
72b31ac9d0 Skin resizable window border 2013-09-29 19:30:10 +00:00
drdev
c957dab3d2 Add digital click to titlebar when full-screen 2013-09-29 19:00:09 +00:00
drdev
f8e522fd1e Allow menu item shortcuts to work when menu hidden 2013-09-29 17:50:08 +00:00
drdev
5ace15ac38 Support Layout > View > Titlebar (F11) to toggle visibility of titlebar 2013-09-29 17:19:05 +00:00
drdev
4f8ef1ef34 Un-maximize when Set Window Size used
Allow dragging titlebar when maximized to un-maximize and move window
2013-09-29 16:43:14 +00:00
drdev
8c580acc42 Prevent being able to resize window when hovering over main menu 2013-09-29 16:22:12 +00:00
drdev
3452039d42 Show icon on titlebar 2013-09-29 16:14:04 +00:00
Chris
20202b08f9 - Added a fluff piece to the changes.txt file.
- Added new card names to changes.txt.
2013-09-29 14:07:48 +00:00
swordshine
fdf0a30843 - Added Chaos Moon, Infernal Darkness and Pale Moon 2013-09-29 13:26:52 +00:00
Sloth
f807986e44 - Added experimental AI support for ProduceMana replacement effects.
- Added Naked Singularity by swordshine.
2013-09-29 10:28:00 +00:00
Maxmtg
358602ad21 developed workaround for bug 744 2013-09-29 09:36:56 +00:00
drdev
321805e786 Support skinned titlebar for main window
Slightly reduce height of main menubar
2013-09-29 06:59:55 +00:00
swordshine
141025a02c - Added Deep Water
- Vanguard: Added Mirri
2013-09-29 05:00:37 +00:00
swordshine
526b86ac16 - Added Pulse of Llanowar 2013-09-29 03:15:02 +00:00
swordshine
0d3eb11aa3 - Mana generation replacement
- Added Mana Reflection
2013-09-29 00:27:46 +00:00
Hellfish
62cf0b5a5e *Corrected an exception message. 2013-09-28 18:17:46 +00:00
Hellfish
0b7238d93b *Allowed multiples of Relentless Rats and Shadowborn Apostles to be added to a commander deck. 2013-09-28 18:02:09 +00:00
dripton
e4e62a8267 Revert r23308 and r23309.
These added synchronized to various methods in Combat,
to fix bugs 740 and 742.  Unfortunately, they could lead
to deadlock, as seen in bug 744.

So this revert fixes bug 744 but reopens bug 740 and bug 740.
2013-09-28 16:19:11 +00:00
Chris
57f95c3c5c - Cleared out the changes.txt file, now ready for new material.
- Added new card names to changes.txt.
2013-09-28 12:46:07 +00:00
Hellfish
28351a1d18 *Added a note to the commander effect indicating which zone the commander is in. Not added to the player panel because space. 2013-09-28 09:01:36 +00:00
Sloth
9b6e34a33f - Fixed DamageDealAi. 2013-09-28 07:22:49 +00:00
Sloth
faad09f594 - Added Mausoleum Turnkey. 2013-09-27 22:25:46 +00:00
Sloth
4b687603f8 - Enforce mandatory targeting when choosing from lists. 2013-09-27 22:24:24 +00:00
swordshine
1084d89cff - Added Scheme: Nature Demands an Offering 2013-09-27 13:35:04 +00:00
Chris
586f6ce5d9 [maven-release-plugin] prepare for next development iteration 2013-09-27 12:41:18 +00:00
Chris
059edfe381 [maven-release-plugin] prepare release forge-1.5.0 2013-09-27 12:41:08 +00:00
Chris
776f4b03ff Minor version number change to the pom file. 2013-09-27 12:31:00 +00:00
Chris
3be5ff1e17 - Preparing the changes.txt file for the next beta build and release. 2013-09-27 12:29:06 +00:00
swordshine
90d51c6dbe - Cleanup 2013-09-26 01:42:39 +00:00
spr
f792bce232 - new overlays menu option. 2013-09-25 18:15:08 +00:00
dripton
94f937ad7e Remove duplicate of the same card name in list. 2013-09-25 16:40:17 +00:00
Sloth
0aacb10d96 - Added draft rankings for Theros (from Le Bestiaire). 2013-09-25 13:57:34 +00:00
Sloth
07b26dd7f3 - Fixed name of Two-Headed Cerberus. 2013-09-25 13:53:35 +00:00
Sloth
6fd8e8de9a - Added another AILogic to ChangeZone effects. 2013-09-25 11:54:38 +00:00
spr
d38898b1dd - #0000743: OutOfMemoryError caused by image ResampleOp in FImagePanel. (http://www.cardforge.org/bugz/view.php?id=743) 2013-09-25 10:15:05 +00:00
Chris
12fd77953f - Added new card names to changes.txt. 2013-09-25 07:39:04 +00:00
drdev
f1db96ddce Improve FSkin.get performance by avoiding type casting 2013-09-25 02:04:54 +00:00
swordshine
537fd9a076 - Fixed Commander combat damage 2013-09-25 01:21:21 +00:00
swordshine
25c18e8f48 - Oracle Changes 2013-09-25 00:32:36 +00:00
Sloth
064d8a8288 - Fixed possible AI crash caused by Polukranos, World Eater. 2013-09-24 21:04:18 +00:00
dripton
8e9fecaf8f Revert r23260
ConcurrentHashMap didn't solve bug 740, so I ended up having to
synchronize methods instead.
2013-09-24 15:45:16 +00:00
dripton
ec75e4d9d9 synchronize all methods that use blockedBands
Fixes bug 742
2013-09-24 15:40:50 +00:00
dripton
4ccf39517b synchronize all methods that use attackedEntities
Fixes bug 740.
2013-09-24 15:30:08 +00:00
swordshine
f26c734c4d - Added Plane: The Maelstrom 2013-09-24 12:00:11 +00:00
RumbleBBU
655c96361e Alternate, more elegant fix to the unlockable editions sorting problem. 2013-09-24 09:42:49 +00:00
Chris
3f4adfbc07 - Added new card names to changes.txt. 2013-09-24 07:53:07 +00:00
swordshine
4d2a2b6ce2 - Fixed Akroan crusader 2013-09-24 05:12:52 +00:00
swordshine
d716493bcd - Converted Oath of Druids, Oath of Ghouls, and Oath of Lieges
- Added Oath of Mages and Oath of Scholars
2013-09-24 00:35:13 +00:00
swordshine
919de9b0fa - Added Conundrum Sphinx 2013-09-24 00:32:01 +00:00
swordshine
a97398e0b3 - Targets in subAbility trigger Precursor Golem now 2013-09-24 00:22:12 +00:00
dripton
2cf046de5e Remove commented-out code. 2013-09-23 16:41:36 +00:00
swordshine
a28e444680 - Fixed Hythonia the Cruel and Soldier of the Pantheon 2013-09-23 12:24:09 +00:00
swordshine
03a3ef8422 - Homeward Path is ready for multiplayer 2013-09-23 11:37:39 +00:00
Sol
f99d1ce51d - Updating Formats for adding Theros next week 2013-09-22 23:21:57 +00:00
Sloth
bf46bd9d96 - Added SVar:PlayMain1:TRUE to 3 Theros cards. 2013-09-22 13:29:38 +00:00
Sloth
5620552344 - AI will now prefer Heroic creatures when casting Auras. 2013-09-22 13:21:15 +00:00
Sloth
3898afe81b - Fixed AI sometimes ignoring "CARDNAME can't be blocked except by three or more creatures". 2013-09-22 11:28:48 +00:00
swordshine
ba9d49d0f1 - Fixed Heroic
- Fixed FightEffect
2013-09-22 08:05:24 +00:00
Sloth
a7f196999f - Improved AI using Shipbreaker Kraken. 2013-09-22 07:02:37 +00:00
swordshine
7493cf2726 - Fixed Heliod, God of the Sun and Shipwreck Singer by Diogenes 2013-09-22 04:14:32 +00:00
swordshine
ffcfd24781 - TargetsValid should find targets in the subabilities 2013-09-22 00:38:44 +00:00
swordshine
bb4f967353 - Prevent NPE 2013-09-22 00:16:52 +00:00
Sloth
8e28ce296a - Replaced some unplayable cards in Jamuraa world quest decks. 2013-09-21 22:08:51 +00:00
Sol
d3b9f4c8d8 - Fix for Anthousa 2013-09-21 17:13:16 +00:00
Sol
5ebdf212e2 - Fix Dragon Mantle, Hammer of Purphoros and Reaper of the Wilds 2013-09-21 17:00:52 +00:00
swordshine
1bc4baffec - Fixed NPE caused by Spatial Merging 2013-09-21 11:35:59 +00:00
swordshine
37661ac0d0 - Fixed the edition file 2013-09-21 11:24:01 +00:00
swordshine
bb7b4c6252 - Fixed Warriors' Lesson 2013-09-21 10:42:00 +00:00
swordshine
aeb6994974 - Improve getLifeThreateningCommanders() 2013-09-21 10:11:23 +00:00
swordshine
892ed7df24 - Fixed getLifeThreateningCommanders() 2013-09-21 09:43:58 +00:00
swordshine
9a60203313 - Cleanup 2013-09-21 02:57:30 +00:00
swordshine
5ce9f79c66 - Updated some scripts to avoid overwriting the original Svars 2013-09-21 02:25:40 +00:00
dripton
0adee5121b Remove some unused imports to fix warnings. 2013-09-20 16:34:33 +00:00
Sol
13070aa96c - Some improvements to Monstrosity text display 2013-09-20 16:29:26 +00:00
dripton
b75012a0a1 Fix spelling of SaTargetRoutines (was SaTargetRountines) 2013-09-20 16:26:43 +00:00
Chris
09c11d1f3f - Added a fluff piece to the changes.txt file. 2013-09-20 13:08:18 +00:00
moomarc
b51bf8f952 - Fixed creature type 2013-09-20 10:27:29 +00:00
Sloth
9db08d508b - Updated some SVars. 2013-09-20 07:57:53 +00:00
Sloth
015aa8f343 - Fixed NPE caused by Bestow. 2013-09-20 07:20:20 +00:00
swordshine
5ed5c9afbe - Fixed several cards with Fading that kept remembered cards 2013-09-20 05:58:23 +00:00
swordshine
f45174318d - Reverted r23257, fixed Duplicant 2013-09-20 04:34:16 +00:00
Sol
a59f5b4d11 - Theros block data 2013-09-20 03:34:03 +00:00
Sol
2e122714b0 - Missed the Theros file to fix typo 2013-09-20 03:29:10 +00:00
Sol
5beba3101e - Fixing issues with Theros cards, adding Oracles that were missed 2013-09-20 03:26:31 +00:00
Sol
c4e0bc0a1a - Updated Oracle for Theros S-Z 2013-09-19 23:55:53 +00:00
Sol
2a0cb56520 - Updated Oracle for Theros N-R 2013-09-19 23:51:59 +00:00
Sol
9e21987039 - Updated Oracle for Theros G-M 2013-09-19 23:51:06 +00:00
Sol
e4fc9164c1 - Updated Oracle for Theros A-F 2013-09-19 23:49:52 +00:00
dripton
f4ce2a9f37 Fix "its" vs. "it's" typo in Sensei's Divining Top. 2013-09-19 21:56:48 +00:00
Sloth
21d7a8fe3f - Merged Theros branch into trunk. 2013-09-19 19:53:33 +00:00
dripton
af8ae84eb5 Add ConcurrentHashMapOfLists and use it for Combat.attackedEntities
Fixes bug 740, intermittent ConcurrentModificationException

The alternative would be synchronizing all access to attackedEntities,
either at the method level or by using an internally synchronized
container like a Hashtable.  But ConcurrentHashMap should give better
performance, since it only locks on writes not reads, and we read
attackedEntities a lot more often than we write to it.
2013-09-19 16:05:15 +00:00
Chris
d73699bcaa - Added new card names to changes.txt. 2013-09-19 08:02:04 +00:00
Sloth
1e55407060 - Fixed NPE caused by High Priest of Penance. 2013-09-19 06:18:10 +00:00
Hellfish
c9987e5efc *Don't Imprint if the card in question isn't exiled.(Really only applicable to commanders) 2013-09-19 05:42:16 +00:00
Sol
4e90eac764 - Simplified Oracle Scraper for magiccards.info 2013-09-19 03:18:34 +00:00
Sloth
24e320237f - AI fix for Monstrosity. 2013-09-18 18:35:30 +00:00
RumbleBBU
0ca16e5996 Fix unlockable sets sorting. 2013-09-18 09:24:36 +00:00
swordshine
5125ef32ab - THS: Added 5 red cards 2013-09-17 12:48:32 +00:00
Chris
9c2c231b93 - Added new card names to changes.txt. 2013-09-17 07:42:19 +00:00
swordshine
35f3949fbc - Implement Rule 702.102d 2013-09-17 02:47:58 +00:00
swordshine
03fb8b24d1 - THS: Added 13 cards 2013-09-17 00:25:23 +00:00
Maxmtg
1ece44afc7 when set was not found, fall back to just card with given name from any set. 2013-09-16 22:39:28 +00:00
moomarc
0b951527fa - Added Blaze of Glory 2013-09-16 15:23:09 +00:00
drdev
b2a86015f1 Fix function name 2013-09-16 08:57:26 +00:00
drdev
f0f5b97e03 Tweak wording on CHANGE.txt 2013-09-16 08:19:24 +00:00
drdev
ba8ec270d1 Support switching between Themes/Skins without restarting 2013-09-16 08:18:19 +00:00
Chris
d3b94b0dd9 - Added new card names to changes.txt. 2013-09-16 08:16:52 +00:00
Chris
7f3a641629 - Added new card names to changes.txt. 2013-09-16 08:14:26 +00:00
moomarc
61073dcd09 - Added Wort, the Raidmother (finally) 2013-09-16 08:03:03 +00:00
drdev
40c840446d Make combo boxes skin correctly 2013-09-16 07:36:39 +00:00
swordshine
6426106e0c - Reverted r23140 2013-09-16 07:04:59 +00:00
swordshine
799387b09b - Fixed Doomsday 2013-09-16 06:51:30 +00:00
Maxmtg
a9e0e660d2 theros edition file 2013-09-16 06:41:59 +00:00
Hellfish
732beaa9fd *Fixed Boseiju, Who Shelters All oracle text 2013-09-16 06:25:20 +00:00
Hellfish
eee4322173 *Converted Commander rules to an effect. (Fixes being unable to cast an Akroma, Angel of Fury commander face down) 2013-09-16 06:14:46 +00:00
swordshine
727920679c - THS: Added 5 cards 2013-09-16 05:47:42 +00:00
swordshine
7ec2d0f8cb - THS: Fixed Bronze Sable 2013-09-16 04:52:59 +00:00
drdev
eb4b25b9e4 Start FComboBox 2013-09-16 03:09:54 +00:00
Chris
a3afd4c35b - Added new card names to changes.txt. 2013-09-15 12:35:36 +00:00
swordshine
cfccacd21d - Template change: "is unblockable" -> "can't be blocked" 2013-09-14 06:25:27 +00:00
swordshine
b40302a3aa - Added Personal Incarnation 2013-09-14 04:17:27 +00:00
Maxmtg
75cd2cdcab restore cards recognition for cases when alias is specified instead of commonly used set code
ddl edition => added 'hvm' alias
2013-09-14 03:39:41 +00:00
swordshine
2f37ca57b4 - Updated the reminder text for Monstrosity 2013-09-14 01:47:22 +00:00
Sloth
e4446b5fa0 - Added the medium quest deck Black Abott 2. 2013-09-13 20:10:04 +00:00
swordshine
9519eb3a17 - Added Truth or Tale 2013-09-13 13:59:55 +00:00
Chris
a1d99ba2ba - Added new card names to changes.txt. 2013-09-13 11:53:20 +00:00
drdev
5c1219f649 Work towards updating combo boxes when theme changes 2013-09-13 07:30:20 +00:00
drdev
e2c773c416 2013-09-13 06:21:49 +00:00
drdev
98d16ba6e1 Fix so disabled background image doesn't come back when changing themes 2013-09-13 06:17:49 +00:00
drdev
a8f3360c40 Update combo boxes in preferences when skin changes 2013-09-13 06:03:25 +00:00
swordshine
c5aa1d3d7e - THS: Added 2 multicolor cards 2013-09-13 05:35:18 +00:00
swordshine
80661d3055 - Added Plane: Stairs to Infinity 2013-09-13 03:55:56 +00:00
swordshine
d4e5a1ffba - Fixed Aladdin's Lamp 2013-09-13 01:48:02 +00:00
swordshine
7b88caee8a - fix r23186 2013-09-13 00:38:00 +00:00
dripton
17f4bbdda1 Apply pqnet's patch to fix bug 704.
(IllegalStateException from using getOpponent() instead of getOpponents())
2013-09-12 23:06:13 +00:00
swordshine
6dacae86f8 - another fix 2013-09-12 14:17:07 +00:00
swordshine
55b182687e - Fixed DigEffect 2013-09-12 13:48:08 +00:00
swordshine
2966504fae - updated the code for the new duel decks 2013-09-12 12:54:48 +00:00
dripton
9df426e427 Fix race in Card.getController()
Fixes bug 737
2013-09-11 21:41:50 +00:00
Chris
f96711fc9a - Added new card names to changes.txt. 2013-09-10 07:31:14 +00:00
swordshine
7d170f4f24 - Converted Freyalise's Winds to script 2013-09-10 04:45:21 +00:00
Sloth
30350041bb - Fixed AI using the ability of Keeper of the Beasts without legal target. 2013-09-09 18:43:55 +00:00
Sloth
bf205b0998 - Added an AILogic for Jace, Architect of Thought. 2013-09-09 17:57:44 +00:00
Sloth
a065c8573f - Expanded AI P/T prediction with +1/+1 counters granting abilities. 2013-09-09 15:27:01 +00:00
Sloth
22c54a488b - AI will no longer cast Scrounge and similar cards when there are no cards to move. 2013-09-09 15:08:25 +00:00
Chris
4b14ff7231 - Added new card names to changes.txt. 2013-09-09 08:48:42 +00:00
moomarc
1bdbfdc7bf - Conspire keyword should be able to stack (just fixed the keywords string, not multiple instances of Conspire actually working properly) 2013-09-09 07:46:59 +00:00
moomarc
7b4812edca - Fixed Recover for Garza's Assassin 2013-09-09 07:27:27 +00:00
swordshine
a6a08bb970 - Added the missing oracle 2013-09-09 06:15:06 +00:00
swordshine
5b47dfbcb7 - Scheme: Added Rotted Ones, Lay Siege 2013-09-09 05:05:12 +00:00
swordshine
d4c54cab89 - Scheme: Added Rotted Ones, Lay Siege 2013-09-09 00:17:45 +00:00
swordshine
358f05ba01 - Updated scripts for commander 2013-09-09 00:15:16 +00:00
drdev
71040279a4 Prevent border around main display panel 2013-09-08 23:27:01 +00:00
drdev
e10843e5cb Fix spacing issues 2013-09-08 22:48:26 +00:00
dripton
98901c4d06 Fix Molder's prompt to reflect that it can target an artifact or enchantment 2013-09-08 18:03:10 +00:00
Chris
12ddd7540b - Added a fluff piece to the changes.txt file.
- Added new card names to changes.txt.
2013-09-08 14:47:53 +00:00
swordshine
565971ac7f - Ruhan of the Fomori can attack specific chosen player now 2013-09-08 13:13:23 +00:00
swordshine
a93ae5b3e9 - Initial code for Bestow
- THS: Added Cavern Lampad, Celestial Archon, Leafcrown Dryad, Nimbus Naiad, Observant Alseid, and Spearpoint Oread
2013-09-08 04:31:50 +00:00
Sol
b662b0a7ba - Clear selected of Partial Paris when Clicking on the "OK" button 2013-09-08 01:22:46 +00:00
dripton
c26e23539b Fix whitespace before I start fixing bug 734 (too many open sound files) 2013-09-07 23:10:22 +00:00
drdev
486a965624 Update fonts when skin changes 2013-09-07 20:06:32 +00:00
Hellfish
f3f44dd65e *Fixed Aladdin's Cave challenge extra cards 2013-09-07 19:21:42 +00:00
drdev
2431e1dd20 Fix crash when updating scaled images
Prevent scaling menu icons to 16x size
2013-09-07 17:27:07 +00:00
drdev
d9eea5d5f0 Ensure status is visible on default menu bar 2013-09-07 17:16:20 +00:00
drdev
9499207bae Fix so menu bar not blanked out when changing skins and status message is retained 2013-09-07 17:06:52 +00:00
drdev
6c6ba5742f Fix so menu bar updated properly when skin changed 2013-09-07 16:36:44 +00:00
Sol
fcf1ac425e - Fix Commander SpellCost not affecting the rest of your spells 2013-09-07 16:32:25 +00:00
swordshine
dba19e6cb7 - Commander spell can be cast from hand if it was bounced 2013-09-07 13:17:34 +00:00
Hellfish
82bb71a34b *Applied Swordshines fix to Commander cost increasing. Thanks! :) 2013-09-07 13:01:29 +00:00
Chris
cd061cd660 - Added new card names to changes.txt. 2013-09-07 12:52:22 +00:00
Hellfish
ea6795df35 *Made Commander casting a spell instead of an ability. Fixes interaction with SpellCast triggers but currently breaks Commander cost increase. 2013-09-07 08:11:35 +00:00
spr
35edfd936b - Fix : tab visibility now updated without using revertLayout() which could alter the state of the active layout and was possibly causing repaint artifacts. 2013-09-07 04:26:58 +00:00
swordshine
e18b3b3537 - Fixed "Play with the top card of your library revealed." for commanders 2013-09-06 14:35:33 +00:00
swordshine
f9d749e2cb - More cards converted to BecomesTarget trigger 2013-09-06 14:14:38 +00:00
swordshine
0baa952db7 - Several cards with "T:Mode$ SpellAbilityCast | TargetsValid$ Card.Self" changed to BecomesTarget trigger 2013-09-06 14:00:58 +00:00
swordshine
fa443f0c7f - ChangeTargets Effect will also trigger BecomesTarget 2013-09-06 13:35:06 +00:00
Chris
98b8c2b43a - Added a fluff piece to the changes.txt file.
- Added new card names to changes.txt.
2013-09-06 11:15:11 +00:00
spr
f5b0fa0467 - update : Card overlays if shown now always shown regardless of card image size. 2013-09-06 10:29:23 +00:00
Sloth
67608ae02a - Updated evaluateCreature to consider Cypher. 2013-09-06 07:31:19 +00:00
dripton
73495c4a76 Slightly improve grammar, from "Discard 1 cards" to "Discard 1 card(s)."
The real fix would involve dynamically adding the 's' depending on the 
current number of cards to discard, but that's more complex.
2013-09-05 23:45:13 +00:00
dripton
3ae5b4cd80 Add null check to avoid NPE
Fixes bug 735
2013-09-05 23:07:53 +00:00
Sloth
6b4de9833f - Fixed cards with "TargetsWithDefinedController$ Targeted" (Memory's Journey and friends). 2013-09-05 14:50:09 +00:00
Sloth
4531658dd8 - Fixed PlayEffect not allowing lands to be played. 2013-09-05 11:37:48 +00:00
Sloth
11e738e6c6 - Fixed possibility of getting stuck with no targets. 2013-09-05 11:27:54 +00:00
Chris
f531baf7d3 - Added new card names to changes.txt. 2013-09-05 11:26:36 +00:00
Sloth
f4e791b6f1 - Fixed multi targeting in DamagePreventAi. 2013-09-05 11:05:43 +00:00
swordshine
c05cd7b2f6 - THS: Added Ashiok, Nightmare Weaver 2013-09-05 07:06:21 +00:00
Maxmtg
9778fe7dfd deck names won't contain slashes any more 2013-09-05 07:01:08 +00:00
swordshine
00208b69a7 - Added Hall of the Bandit Lord 2013-09-05 04:05:34 +00:00
swordshine
3df8397c9a - Add Monstrosity to unparsed abilities 2013-09-04 13:42:04 +00:00
swordshine
b480c2895c - THS: Added Hundred-Handed One 2013-09-04 12:59:34 +00:00
Chris
928b4fa887 - Added new card names to changes.txt. 2013-09-04 12:27:49 +00:00
swordshine
4a8ff10965 - keyword "CARDNAME can't attack during extra turns." for Medomai the Ageless 2013-09-04 11:49:59 +00:00
swordshine
23df025313 - Updated Urza's Incubator 2013-09-04 10:51:09 +00:00
spr
aa4ee6ec6b - Card Overlays option added to Game menu. 2013-09-04 07:29:36 +00:00
Hellfish
a0ca0c47c9 *Reintroduced Dig changes for Interplanar Tunnel (less broken this time. JINX!) 2013-09-04 06:21:19 +00:00
Hellfish
775879096f *Removed 'Random' option from Commander and Planar deck choice when there are no such decks. 2013-09-04 06:09:54 +00:00
swordshine
8969d07d45 - Added Card Edition "Duel Decks: Heroes vs. Monsters"
- Added Xenagos to the Planeswalker type
2013-09-04 05:58:28 +00:00
Sol
b1fd15c083 - Fix DigEffect trying to use library position for all zones 2013-09-04 02:41:06 +00:00
swordshine
b9f4a9abda - THS: Added Ember Swallower, Reverent Hunter, Sealock Monster, Stoneshock Giant, and Sylvan Caryatid 2013-09-04 00:19:33 +00:00
drdev
fc5230a9b9 Fix rendering of scaled images 2013-09-04 00:18:06 +00:00
drdev
9f6f9ce7bb Prevent stack overflow when setting regular cursor 2013-09-03 22:58:00 +00:00
drdev
b9d5cc4ae7 Fix so scrollbars and buttons aren't messed up when skin changed 2013-09-03 22:47:28 +00:00
drdev
cfed3ee53a Fix progress bar messages 2013-09-03 22:16:33 +00:00
drdev
a242fbd8a2 Support updating icons, images, and cursors when skin changed 2013-09-03 21:55:47 +00:00
Sloth
b1a8accfd0 - AI will no longer destroy stolen permanents when the stealing aura can be destroyed. 2013-09-03 20:57:01 +00:00
Chris
905d64cb51 - Added new card names to changes.txt. 2013-09-03 13:02:25 +00:00
Maxmtg
ab2d7ae029 might prevent NPE choosing deck 2013-09-03 06:19:36 +00:00
Maxmtg
2b33f588bd clear up warnings,
remove useless setters in RegisteredPlayer
a few style clean ups in cardfactory/CardFactoryUtil.java
2013-09-03 06:14:17 +00:00
swordshine
3c86a85cd3 - Fixed Monstrosity 2013-09-03 00:38:42 +00:00
Sloth
b4ae3c97a3 - Improved TokenAI. 2013-09-02 20:25:28 +00:00
Sloth
80dd6a2efa - Fixed AI removing the last +1/+1 counter from Golgari Grave-Troll to regenerate. 2013-09-02 20:15:04 +00:00
Sloth
5069854924 - Fixed Trial//Error. 2013-09-02 20:08:30 +00:00
Sloth
29d4875778 - Fixed Canopy Cover. 2013-09-02 19:52:50 +00:00
Hellfish
df44f00eb8 *For some reason I thought the commander blocking hadn't been committed. Removing duplicate code. 2013-09-02 19:10:31 +00:00
drdev
1a08cd6ae4 Support caching derived colors for reuse and actually updating colors when skin changed 2013-09-02 16:28:02 +00:00
drdev
2f39539135 Prevent crash when changing skin 2013-09-02 16:00:06 +00:00
drdev
8350c398f3 Support changing skin on the fly 2013-09-02 15:44:00 +00:00
Chris
3591759cdb - Added new card names to changes.txt. 2013-09-02 13:32:11 +00:00
Chris
f352704fcd - Cleared out the changes.txt file, now ready for new material. 2013-09-02 12:58:56 +00:00
swordshine
679c0fea03 - THS: Added Flamespeaker Adept (TriggerScry) 2013-09-02 08:00:41 +00:00
Hellfish
370927e489 *Reintegrated Commander branch 2013-09-02 05:51:40 +00:00
swordshine
d675097da5 - THS: Added Nessian Asp 2013-09-02 05:47:03 +00:00
drdev
e4c7797102 Fix background of home screen 2013-09-01 23:11:03 +00:00
drdev
57a9c54630 Refactor skin colors and support tracking skinned components 2013-09-01 19:35:46 +00:00
drdev
9c885af7ec 2013-09-01 19:16:21 +00:00
Hellfish
242d293f5a *Fixed stupidity in commander blocking 2013-09-01 18:06:47 +00:00
Hellfish
bc44426d9d *Expanded AF_Dig
*Added the Planechase phenomenon Interplanar Tunnel
2013-09-01 13:57:56 +00:00
Sloth
e4441f9126 - Fixed Simic Manipulator with X=0. 2013-09-01 12:37:51 +00:00
swordshine
97855b563d - THS: Added Nylea, God of the Hunt (God is now added to the creature type list) 2013-09-01 11:56:17 +00:00
Hellfish
7ef5462969 *Fixed card text displaying commander damage dealt. 2013-09-01 09:24:46 +00:00
Hellfish
714dbdc930 *Made AI at least semiconscious of blocking commanders 2013-09-01 09:06:17 +00:00
swordshine
f1366a5e8a - THS: added Polukranos, World Eater and Nykthos, Shrine to Nyx 2013-09-01 08:56:06 +00:00
Hellfish
5af7659883 *Merged from trunk up to r23056 2013-09-01 07:31:14 +00:00
Sloth
05df1adb11 - Updated the quest deck Mortivore 3. 2013-08-31 06:56:46 +00:00
Sloth
05066496d8 - Fixed Extort description. 2013-08-30 22:07:31 +00:00
Sloth
599821c018 - Fixed Saproling Burst. 2013-08-30 22:02:04 +00:00
Sloth
52cae0e723 - Fixed Scalpelexis. 2013-08-30 21:46:04 +00:00
Sloth
ceb9180bbe - Fixed Fire Juggler. 2013-08-30 21:43:41 +00:00
Sloth
657beeb63d - Fixed Diregraf Escort. 2013-08-30 18:33:11 +00:00
Chris
16a915faa1 [maven-release-plugin] prepare for next development iteration 2013-08-30 12:32:46 +00:00
Chris
2ca3d67eb5 [maven-release-plugin] prepare release forge-1.4.7 2013-08-30 12:32:36 +00:00
Chris
309cf6658d - Preparing the changes.txt file for the next beta build and release. 2013-08-30 12:20:13 +00:00
spr
e21aac63f4 - Added "How to Play" option to Help menu which links to the cunningly hidden "res\howto.txt". 2013-08-30 04:36:40 +00:00
Sloth
9a0b240e73 - Fixed Vanish into Memory. 2013-08-29 21:20:34 +00:00
swordshine
046f3a6458 - Fixed Swords to Plowshares (a LKI issue caused by TargetedController when some cards changed the controller of the creature) 2013-08-28 14:28:01 +00:00
Sloth
398a3ed1ea - Fixed possibility of getting stuck with Tangle Wire. 2013-08-28 11:12:08 +00:00
Sloth
f1ab723957 - Fixed LKI's of tokens not being marked as tokens. 2013-08-28 10:00:39 +00:00
Sloth
9380a06a35 - Fixed Soul Barrier. 2013-08-28 09:56:14 +00:00
swordshine
73131ba99e - Fixed Mikaeus, the Unhallowed 2013-08-28 04:02:40 +00:00
Sloth
12e813a5a1 - Fixed Raid Bombardment. 2013-08-27 20:49:50 +00:00
Chris
de87fcb123 - Added new card names to changes.txt. 2013-08-27 12:24:27 +00:00
swordshine
26b4eb000a - Fixed typo 2013-08-27 12:19:21 +00:00
swordshine
4c9dacbcbe - move the new THS card to the branch 2013-08-27 12:09:41 +00:00
swordshine
29a959fb59 - Initial implementation of the new keyword ability Monstrousity and its related trigger
- Theros: Added Shipbreaker Kraken
2013-08-27 10:36:25 +00:00
Sloth
b5049bbb8d - Added an easy version of the Galadriel quest deck by Nordos. 2013-08-27 09:23:53 +00:00
Sloth
42ab52e791 - Added a mandatory AI part to ControlGainAI. 2013-08-27 09:23:11 +00:00
swordshine
999070fd9b - Fixed Quicksilver Fountain by squee1968 2013-08-27 01:27:08 +00:00
spr
03343276c2 - Declare blockers fix added to CHANGES.txt. 2013-08-26 17:52:47 +00:00
spr
02e7a8920a - Additional CHANGES.txt updates. 2013-08-26 17:29:00 +00:00
spr
fbc2d6ad7e - Duel/Match screen changes detailed. 2013-08-26 17:22:40 +00:00
Sloth
7ce642fa7b - Unlikely but possible case fixes in player hasProperty. 2013-08-26 09:07:52 +00:00
Hellfish
8c984d6dbe *Merged from trunk up to r23024 2013-08-26 05:59:14 +00:00
spr
9c0dad1342 - Added menu option to toggle sound on/off from within a duel. (http://www.cardforge.org/bugz/view.php?id=727) 2013-08-26 03:03:30 +00:00
Chris
dc69975b87 - Added a fluff piece to the changes.txt file. 2013-08-26 02:18:20 +00:00
spr
da06932d85 - Fix : 0000726: Picture preview panel does not adhere to Scale Image Larger setting. (http://www.cardforge.org/bugz/view.php?id=726 2013-08-26 02:01:59 +00:00
Sloth
4cfed1e438 - Updated some SVars. 2013-08-25 21:44:58 +00:00
spr
8c461edc1d - Fix : MenuBar was not disabling when overlay was active. 2013-08-25 21:00:41 +00:00
spr
ed7d5aa8af - Game Log Console efficiency & UI updates. (see http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=11561) 2013-08-25 20:26:54 +00:00
drdev
66a4b444e3 Fix spacing issue 2013-08-25 17:40:31 +00:00
drdev
cebcd25f81 Add Theme submenu to Layout menu 2013-08-25 17:38:58 +00:00
drdev
48ba246fc4 Make Layout menu available on all screens (with certain options hidden based on screen)
Move Set Window Size out of Dev Mode pane/menu and into Layout menu
2013-08-25 14:43:36 +00:00
Sloth
16a2e75304 - Fixed groupAndOrderToPayShards using an unordered list. 2013-08-25 06:47:07 +00:00
moomarc
eb1d4a6354 - Updated quest icon avatar download list 2013-08-24 18:08:58 +00:00
Chris
490abc5826 - Added new card names to changes.txt. 2013-08-24 12:07:03 +00:00
spr
90c78a469d - Fix: Prompt heading was not being reset to "Setup Game" on new game. 2013-08-24 11:44:26 +00:00
spr
3225112fc7 - Fix: Prevent buttons in REPORT_MESSAGE panel from disappearing off screen as the panel size is decreased.
- Cosmetic updates to appearance.
2013-08-24 11:17:01 +00:00
Sloth
20732ab927 - Added the quest deck Leela 2. 2013-08-24 10:34:46 +00:00
Maxmtg
501a0ce009 fix: concede during declare blockers 2013-08-24 09:21:00 +00:00
moomarc
95af0e1ef5 - Added Plane: Grand Ossuary 2013-08-23 14:59:19 +00:00
moomarc
92a7c1a20a - Removed duplicate svar from Predator Ooze 2013-08-23 10:32:09 +00:00
Sloth
c51abaaaf4 - Added the name of the source card to some targeting prompt messages. 2013-08-23 09:35:45 +00:00
swordshine
1df24c0287 - Fixed TriggerDescription 2013-08-23 08:47:07 +00:00
swordshine
22e6603a5b - Converted keyword "Whenever a creature dealt damage by CARDNAME this turn is put into a graveyard, put a +1+1(+2/+2) counter on CARDNAME." to script 2013-08-23 08:43:10 +00:00
Maxmtg
8c07e0b89f gave names to all IStorage instances, fixed display of card list for decks in subfolders 2013-08-23 05:40:24 +00:00
drdev
4be5d02fd7 Make progress on UI for ItemManager filters 2013-08-23 01:06:03 +00:00
Sloth
d6176eec0d - Fixed Hunting Wilds. 2013-08-22 18:42:20 +00:00
spr
2279087023 - Added option to hide panel tabs to Layout menu. 2013-08-22 16:35:52 +00:00
Chris
59197f2ac9 - Added new card names to changes.txt. 2013-08-22 12:24:37 +00:00
swordshine
2af8c51798 - Added Necrotic Plague and Quicksliver Fountain
- Plane: Added Immersturm
2013-08-22 11:49:15 +00:00
spr
a05fbe5cd8 - Game, Layout and DevMode menus added to match screen menu bar. 2013-08-22 10:32:06 +00:00
Sloth
30f12050dc - Fixed Cytoplast Root-Kin. 2013-08-22 07:59:58 +00:00
spr
fdc97228e7 - Menu bar visibility can be toggled using F1 key.
- Hints or status info can be displayed on the right-hand side of the menu bar.
2013-08-22 07:50:04 +00:00
Sloth
ca9266f772 - Added Erithizon. 2013-08-21 21:58:41 +00:00
Sloth
d1eeeedca1 - The bounce ability of Sunken Hope is now mandatory. 2013-08-21 16:31:52 +00:00
Sloth
39486f078c - Added the quest deck Havok 3 by Nordos. 2013-08-21 16:19:35 +00:00
swordshine
c6904e740c - Added Karplusan Minotaur 2013-08-21 14:20:58 +00:00
Chris
626279667f - Added new card names to changes.txt. 2013-08-21 12:31:20 +00:00
Sloth
c1d8cccdb3 - Moved check for TargetsWithDefinedController to the right place. 2013-08-21 11:57:14 +00:00
swordshine
db8a191afb - Added Mossbridge Troll 2013-08-21 09:06:48 +00:00
Sloth
e8099b8492 - Added Death Match. 2013-08-21 08:36:12 +00:00
Sloth
5bcea269de - Added some basic handling for tokens to the copyCard function (looks like it's used in places where this is necessary). 2013-08-21 08:23:28 +00:00
Sloth
9fe331d11a - Fixed Thoughtbound Primoc crashing the game . 2013-08-21 08:03:43 +00:00
Hellfish
cbeb5ced09 *Merged from trunk up to r22981 2013-08-21 06:17:56 +00:00
Hellfish
03ef46b316 *Basic land handling in cmd deck editor 2013-08-21 05:53:46 +00:00
Hellfish
66de7141d9 *Com Deck editor basic lands handled.
*Preliminary commander damage blocking ai (not yet fully in effect)
2013-08-21 05:30:28 +00:00
swordshine
8f80f4d439 - Added Illuminated Folio 2013-08-21 05:05:55 +00:00
drdev
4b30a7eca1 Make progress on API for new ItemManager filters
Create LayoutHelper and TypeUtils classes
2013-08-21 02:25:13 +00:00
spr
5ee58d727a - New forge menu bar (part 1 of 2). Includes common Forge and Help menus. 2013-08-21 01:13:26 +00:00
swordshine
f8f03a57ba - More cards ready for multiplayer 2013-08-21 00:42:32 +00:00
spr
34618e957c - Forge forced to use Java cross platform "Metal" look and feel which should only really affect Mac users (in a good way hopefully!). More details, see http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=11126#p125845. 2013-08-20 05:26:24 +00:00
Sol
f745ba9f5d - Updating Svar 2013-08-20 02:56:20 +00:00
drdev
1677e1eeb1 Refactor so ItemManager is abstract and ItemFilter doesn't extend JPanel
Start working on transitioning card color/type filters
2013-08-20 00:07:39 +00:00
Maxmtg
eebbb45b2c better display of subfolders 2013-08-19 22:48:33 +00:00
Maxmtg
ada5fa5056 Subfolders for constructed decks work in r/o mode (part 2 of ~3) 2013-08-19 22:40:10 +00:00
Maxmtg
df6860294d Support for nested folders that store constructed decks - part 1 of ≈3 2013-08-19 20:48:21 +00:00
Sloth
8b97275195 - The AI will now trade blockers with attackers like Goblin Lackey more aggressively. 2013-08-19 11:13:17 +00:00
Hellfish
7dfde0aba3 *Somehow missed merging some revisions 2013-08-19 08:33:32 +00:00
Hellfish
517b1ae328 *Merged changes from trunk
*Tarted up the Commander Deck Editor
2013-08-19 08:20:08 +00:00
drdev
7e92f73a30 Add ItemFilter.java (not committed by accident) 2013-08-18 23:09:31 +00:00
drdev
1514ad0694 Create ItemFilter class 2013-08-18 23:05:15 +00:00
Maxmtg
48bebfef05 interface for nested folders support in Istorage 2013-08-18 21:21:47 +00:00
Hellfish
19cb78e529 *Made Commander casting sorcery speed. 2013-08-18 21:06:58 +00:00
Maxmtg
ddb66257ce * Following the ideas implemented in java.util.collections, IStorageView interface removed, the base class is read-only by default.
* isUnique removed for being  synonym for !contains
* QuestDeckMap inherited correctly from StorageBase to avoid duplicate code.
2013-08-18 20:16:09 +00:00
Hellfish
fded944458 *Preliminary Mana replacement 2013-08-18 20:08:03 +00:00
Hellfish
e55d5f8199 *Fixed Command Tower in non-Commander scenarios 2013-08-18 19:47:33 +00:00
Hellfish
e8dc32489d *Fixed Commander deck editor 2013-08-18 19:43:44 +00:00
Hellfish
f8ec884788 *Initial Commander checkin
*Everything implemented *except* mana replacement
*Added Command Tower
2013-08-18 19:15:53 +00:00
Hellfish
fa0fb3e519 *Created Commander branch 2013-08-18 19:14:05 +00:00
Maxmtg
023bc56986 TableSorter renamed to ItemPoolSorter and moved to ItemPool's package - this also removes an incorrect reference from Deck to gui. 2013-08-18 19:10:54 +00:00
drdev
c52050e37c Make all text fields select all on focus 2013-08-18 19:03:30 +00:00
Chris
a523f3c130 - Added new card names to changes.txt. 2013-08-18 16:34:34 +00:00
Chris
0126e6dae3 - Cleared out the changes.txt file, now ready for new material. 2013-08-18 14:02:20 +00:00
Sloth
2a991e219f - Fixed being able to discard tokens. 2013-08-18 11:51:35 +00:00
Sloth
3c31e29a48 - Tokens leaving the battlefield will now also create LKI copies. 2013-08-18 10:45:20 +00:00
Sloth
e7a143e181 - Fixed Exalted Dragon. 2013-08-18 08:12:48 +00:00
Sloth
03b37b049b - Fixed overeager RuntimeException caused by Coalition Relic. 2013-08-18 06:49:03 +00:00
drdev
1563cb4693 Refactor Card Catalog and Current Deck to use new ItemManager control
Support ghost text in FTextFields and add "Search" ghost text for Card Catalog field and use in place of old "[New Deck]" logic
Standardize size of buttons and text fields a bit
Allow horizontal scrollbar for card tables in Deck Editor if preference for elastic columns not on
2013-08-18 06:13:15 +00:00
drdev
82f32154fd Make search in buttons be same size as Add Filter button 2013-08-18 00:10:58 +00:00
drdev
37b13aa484 Add parameterless function for setting to show ghost text with focus 2013-08-18 00:06:22 +00:00
drdev
cf00e02c12 Change ghost text default to be hidden with focus
Add ghost text to Card Catalog search field
2013-08-17 23:04:40 +00:00
drdev
4bfb8a840a Prevent crash when hovering over table header to right of final column 2013-08-17 22:38:55 +00:00
drdev
490efdbd5a Hide search box for now in Item Manager 2013-08-17 22:33:44 +00:00
Sloth
d2335d2382 - Converted Magus of the Abyss to script. 2013-08-17 20:59:09 +00:00
Sloth
7e8a729fed - Added Ley Line. 2013-08-17 20:56:33 +00:00
Sloth
d6274b42af - Added Pandemonium. 2013-08-17 20:37:09 +00:00
Sloth
699a502cc5 - Added the option for targets of triggers to be chosen by another player.
- Converted The Abyss to script.
2013-08-17 19:21:33 +00:00
drdev
292bfed82a Don't auto-resize columns if want elastic columns set to false 2013-08-17 16:22:05 +00:00
drdev
a34847ab7b Remove table functions from ItemManager class 2013-08-17 16:01:02 +00:00
drdev
9e5f7f943b User doLayout override to layout ItemManager components instead of resize handler 2013-08-17 15:13:33 +00:00
drdev
7dc9afddb3 Make ItemManager derive from JPanel instead 2013-08-17 15:10:26 +00:00
Sloth
f174d73098 - State effects are now checked after a land is played (fixes the legend rule). 2013-08-17 10:45:37 +00:00
Sloth
15c90a00cd - Fixed cleanup of phased out cards. 2013-08-17 10:38:55 +00:00
swordshine
1ecb6728b1 - Converted static ETB triggers to replacement effects 2013-08-17 05:26:48 +00:00
swordshine
f13c38e135 - Fixed replacement effects for cloned cards 2013-08-17 04:55:13 +00:00
swordshine
7c34056281 - Converted Virulent Wound to script 2013-08-17 04:03:51 +00:00
swordshine
0fb1c82dc0 - Fixed Heat Stroke and Defiant Vanguard 2013-08-17 02:59:10 +00:00
drdev
ecd8e8838f Fix layout in Item Manager 2013-08-17 02:46:37 +00:00
drdev
cd969babff Increase text field height by 2 pixels 2013-08-17 01:53:21 +00:00
drdev
116a24ac23 Support ghost text in text fields and make them look nicer 2013-08-17 01:49:36 +00:00
swordshine
a951e4eaa0 - Remove hidden extrinsic keyword "At the beginning of the end step, destroy/exile CARDNAME." after it triggers. 2013-08-17 00:54:57 +00:00
Sloth
fa820d4e07 - Fixed ControlGain effects not lasting until Cleanup step. 2013-08-16 22:01:31 +00:00
Sloth
f0ab756a5d - Fixed AttackersDeclared triggering without attackers. 2013-08-16 20:14:07 +00:00
Sloth
6545960635 - Fixed typo in Vanishing description. 2013-08-16 20:02:10 +00:00
spr
ef8bb689d7 - ComboBox style now set globally via UIManager which should hopefully resolve issue with Mac (see http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=11126#p125845). 2013-08-16 19:45:42 +00:00
Chris
ce988a66df [maven-release-plugin] prepare for next development iteration 2013-08-16 13:09:31 +00:00
Chris
54d98f710c [maven-release-plugin] prepare release forge-1.4.6 2013-08-16 13:09:20 +00:00
Chris
c1ab52e38f - Preparing the changes.txt file for the next beta build and release. 2013-08-16 12:57:41 +00:00
swordshine
42f6c88463 - made the hidden keyword "At the beginning of the end step, sacrifice CARDNAME." permanent 2013-08-16 12:27:27 +00:00
swordshine
d85e43a427 - Converted "At the beginning of your end step, return CARDNAME to its owner's hand." to a trigger 2013-08-16 12:17:33 +00:00
Sloth
d74f571ddb - Little improvement for exalted in isEffectiveAttacker. 2013-08-15 20:25:00 +00:00
swordshine
cfcbde62b2 - More cards ready for multiplayer
- Converted several cards to replacement effects
2013-08-15 04:55:40 +00:00
swordshine
f3fbb82dbd - Converted two cards to replacement effects 2013-08-15 00:34:45 +00:00
swordshine
0a0c9d8070 - More cards ready for multiplayer 2013-08-14 14:10:32 +00:00
drdev
27e22ace02 Support horizontal scrolling item manager container 2013-08-14 02:43:26 +00:00
Sol
5b7197fbd6 - Fire SpellResolve event before the spell gets reset 2013-08-13 02:27:04 +00:00
swordshine
a1764bf043 - More cards ready for multiplayer 2013-08-13 00:19:12 +00:00
Sloth
cfc280274f - Expanded AI use of damageFromETB (Poisonbelly Ogre, Suture Priest, etc.). 2013-08-12 18:54:06 +00:00
Chris
258953ec53 - Added new card names to changes.txt. 2013-08-12 12:16:53 +00:00
Sloth
236d528923 - Some improvements for damageFromETB. 2013-08-12 11:45:24 +00:00
Sloth
3fd6c7b688 - The AI will no longer kill itself with Ankh of Mishra and similar cards. 2013-08-12 10:59:17 +00:00
drdev
3306fb4352 Make ItemManager transparent 2013-08-12 01:24:15 +00:00
drdev
c0960c2250 Start working on having ItemManager render controls 2013-08-12 01:18:57 +00:00
swordshine
651fd5e5a2 - Converted Power Surge
- Added Monsoon
2013-08-12 00:20:15 +00:00
drdev
4d72113af4 Fix crash when opening Deck Editor 2013-08-12 00:08:12 +00:00
drdev
ca6fe5105c Remove need for ITableContainer 2013-08-11 23:48:18 +00:00
Sol
27c19645ac - Simplify refundManaPaid to fix infinite Mana issue with Sol Ring 2013-08-11 22:43:16 +00:00
drdev
259086a979 More refactoring 2013-08-11 19:51:33 +00:00
drdev
9f4ba4b50c Update names of properties/variables 2013-08-11 18:47:46 +00:00
drdev
21ad3cfc5e Rename ListView to ItemManager 2013-08-11 18:31:05 +00:00
Sloth
bfba7a5102 - Fixed Whip Vine. 2013-08-11 18:13:51 +00:00
drdev
0119fcd24a Get deck editor working again 2013-08-11 18:06:00 +00:00
asepetci
39edeacc5b updated rankings.txt 2013-08-11 14:20:04 +00:00
Sloth
e0dcec7d5b - Fixed combat tab always showing during combat even when the stack is not empty. 2013-08-11 13:23:04 +00:00
Sloth
3ec7071889 - Fixed Joven's Ferrets. 2013-08-11 13:01:55 +00:00
Sloth
eeb411bacb - Added some mandatory targeting code to the default doTriggerAINoCost. 2013-08-11 08:31:51 +00:00
Sol
03d13ebbc1 - Fix Reflecting Pool bug, activating player wasn't being set to controller which means the second Reflecting Pool wasn't properly filtering what it could reflect 2013-08-11 04:06:37 +00:00
drdev
46bb7df90e Update variable name 2013-08-11 03:00:48 +00:00
drdev
69ed6496a6 Refactor Deck Editor to use ListView and get compile working 2013-08-11 02:54:50 +00:00
drdev
b100daea1b Get compile working 2013-08-10 23:54:05 +00:00
Sloth
ec38900386 - Added the easy quest opponent Cohen the Barbarian 1. 2013-08-10 21:09:40 +00:00
drdev
a523cdf1e6 Start setting up ListView views and models 2013-08-10 17:01:44 +00:00
drdev
c552ef1616 Make ItemPool and ItemPoolView more generic 2013-08-10 15:56:24 +00:00
drdev
752a4413f3 Make more generic ListView 2013-08-10 14:32:33 +00:00
Chris
186bd2d7da - Added new card names to changes.txt. 2013-08-10 12:37:23 +00:00
swordshine
610c2f98ce - Fixed Isochron Scepter not imprinting Research // Development 2013-08-10 12:02:29 +00:00
spr
fdac56a9a7 - Fix : Selected human & AI avatars now remain in fixed position when scrolling list of available avatar images. 2013-08-10 05:55:39 +00:00
swordshine
2bb8c97a5a - An accurate script of Ashling, the Extinguisher Avatar
- Karn's Ultimate can be used in Variants
2013-08-10 04:48:51 +00:00
swordshine
ad3b034fd2 - Fixed Caged Sun by Zirbert 2013-08-10 00:48:31 +00:00
swordshine
b2b1657e31 - Converted Ripple to script 2013-08-10 00:34:24 +00:00
spr
3a4087a2e1 - Zoomer now supports split, flip and double-sided cards including cards played face down. (see CHANGES.txt for full details). 2013-08-09 20:53:28 +00:00
Sloth
3fe7d3adf3 - Added Rasputin Dreamweaver. 2013-08-09 18:40:26 +00:00
swordshine
f64566dbae - More card scripts updated 2013-08-09 13:38:24 +00:00
swordshine
98f3a50fbd - Fixed Eunuchs' Intrigues 2013-08-09 13:15:23 +00:00
Chris
ef5234d734 - Added new card names to changes.txt. 2013-08-09 12:38:45 +00:00
Sloth
5b34571cf2 - Fixed script of Pyromancer Ascension. 2013-08-09 11:42:58 +00:00
swordshine
edac489791 - Updated scripts 2013-08-09 10:11:05 +00:00
swordshine
acb01268da - Fixed Mask of Intolerance 2013-08-09 08:12:12 +00:00
swordshine
9b19a88a14 - Update card scripts 2013-08-09 07:44:35 +00:00
swordshine
555a8bfed7 - Fixed targeted SpellAbility for some cards with counter effect (please review) 2013-08-09 07:39:50 +00:00
swordshine
b4efa372b2 - More cards ready for multiplayer 2013-08-09 04:54:45 +00:00
swordshine
8ee5a5fa41 - More cards ready for multiplayer 2013-08-09 03:14:12 +00:00
drdev
c77f70b371 Get compile working 2013-08-09 01:54:30 +00:00
swordshine
c4f1721250 - Added Harsh Judgment
- More cards ready for multiplayer
2013-08-09 00:32:01 +00:00
Sol
32d2f98bef - Mana Abilities with subAbilities won't auto choose in express mana payment 2013-08-09 00:29:22 +00:00
Sloth
1f40291c1b - Added some AITapDown SVars. 2013-08-08 20:08:23 +00:00
Sloth
8b3dda264b - Added Citadel of Pain by lazylockie. 2013-08-08 19:59:02 +00:00
moomarc
1a5c749b15 - Improved script of Venser, Shaper Savant 2013-08-08 18:34:20 +00:00
moomarc
0afd72195b - Added Clockspinning (including supporting AI) 2013-08-08 17:40:54 +00:00
swordshine
fef2c873d5 - Fixed DigAi for Guild Feud (issue 719, http://cardforge.org/bugz/view.php?id=719) 2013-08-08 12:57:25 +00:00
Chris
7d0fdfff24 - Added new card names to changes.txt. 2013-08-08 12:36:39 +00:00
swordshine
2d966bc535 - More cards ready for multiplayer 2013-08-08 12:25:30 +00:00
swordshine
4b3c8b555e - More cards ready for multiplayer 2013-08-08 12:01:34 +00:00
swordshine
c0de6f7721 - Simplified Standstill's script 2013-08-08 10:25:05 +00:00
swordshine
70323eccd0 - Added Edition "From the Vault: Twenty" by dmacgamer 2013-08-08 08:26:52 +00:00
swordshine
b259c66530 - Updated the script of Doomsday (should be affected by "can't search library" effect) 2013-08-08 07:19:00 +00:00
moomarc
84bbd884ae - Added Stranglehold 2013-08-08 06:46:26 +00:00
swordshine
fb3867c21c - Converted Recover to script 2013-08-08 06:37:32 +00:00
moomarc
0b61e6b4b6 - fixed to Eater of Days 2013-08-08 06:05:10 +00:00
swordshine
fbad22131a - Cleanup 2013-08-08 05:08:28 +00:00
spr
884232bd33 - Fix : NPE on zooming a card with no rules (eg. human creature token). 2013-08-08 04:49:46 +00:00
swordshine
70acd483d1 - Converted the upkeep trigger of Obsidian Fireheart to script 2013-08-08 04:41:43 +00:00
swordshine
018699cc39 - Updated scripts 2013-08-08 03:57:54 +00:00
swordshine
61d1786a00 - Converted "At the beginning of the end step, sacrifice CARDNAME.", can be targeted by Stifle 2013-08-08 03:17:59 +00:00
drdev
07c38892f9 Start breaking off files for CardListView 2013-08-08 03:17:55 +00:00
Sol
f689d44f01 - Convert skip your next turn cards from giving an extra turn to an opponent (since that's wrong) 2013-08-08 02:26:47 +00:00
swordshine
ce53c8d84a - Converted Swans of Bryn Argoll to script 2013-08-08 02:04:20 +00:00
swordshine
d1030df71a - Updated Hydra Omnivore 2013-08-08 01:43:12 +00:00
swordshine
d9be047a5c - Converted Tectonic Instability to script 2013-08-08 01:24:48 +00:00
swordshine
2966a22521 - Fixed Sparkcaster 2013-08-08 01:10:58 +00:00
swordshine
79d5f9b46d - Cleanup unnecessary text in card scripts
- Fixed Taoist Mystic
2013-08-08 00:32:57 +00:00
swordshine
9f387d1ddf - Converted Vanishing to script
- Updated Maelstrom Djinn
2013-08-08 00:21:27 +00:00
drdev
f32a1455fd 2013-08-08 00:05:09 +00:00
Sloth
1dcc84a894 - Added Stoneshaker Shaman by lazylockie. 2013-08-07 20:31:30 +00:00
Sloth
1309a14ea0 - Added some basic AI to support and added Copperhoof Vorrac by marc. 2013-08-07 20:20:00 +00:00
drdev
04010be62a Update CHANGES.txt with note about default window size/position 2013-08-07 15:50:47 +00:00
drdev
d8319fd89e Remember window size/position between sessions 2013-08-07 15:37:48 +00:00
moomarc
0dbd8155ae - Added Rock Jockey 2013-08-07 15:27:15 +00:00
moomarc
f426c6be3d - Small comment added to changes.txt about the new preference 2013-08-07 15:02:12 +00:00
swordshine
88d32d6c4a - Converted Fading to script (now can be stifled and copied by Strionic Resonator) 2013-08-07 13:10:08 +00:00
drdev
784e8ba0eb Make arrow icons appear the same size for all 4 directions 2013-08-07 12:54:23 +00:00
swordshine
196ea025a9 - entersBattleFieldWithCounters command converted to etbCounter keyword 2013-08-07 12:16:03 +00:00
swordshine
654f64b604 - reverted some changes because r22822 fixes most of the cases 2013-08-07 12:13:32 +00:00
Chris
b568fdd47b - Added new card names to changes.txt. 2013-08-07 11:27:23 +00:00
Sloth
45e72f9202 - Fixed stale card objects of cards leaving the battlefield having the wrong currentZone variable. 2013-08-07 11:05:31 +00:00
Sloth
4f71f79bb4 - Added Heat Wave. 2013-08-07 10:23:20 +00:00
spr
9aa52d7094 - Added Release Notes section under the Game Settings menu.
- FSkin now offers a fixed font.
2013-08-07 02:40:27 +00:00
Sloth
267dfa7545 - Fixed type of Vastwood Hydra. 2013-08-06 20:16:40 +00:00
spr
f8a086a26b - Split cards (name contains "//") are now rotated 90 degrees in zoomer. 2013-08-06 18:05:52 +00:00
moomarc
c78689db89 - Added War Cadence
- new preference for enabling/disabling the prompt for block costs of 0.
2013-08-06 17:39:37 +00:00
Sloth
44b4e699aa - Script cleanup of Cocoon. 2013-08-06 14:03:13 +00:00
Chris
e3a88484c6 - Added new card names to changes.txt. 2013-08-06 11:49:39 +00:00
swordshine
f2e710f94d - experimental fix of ChangeZoneEffect.changeKnownOriginResolve and SacrificeEffect
- Added Traveling Plague
2013-08-06 11:44:01 +00:00
Sloth
7df35a6203 - Added Cocoon. 2013-08-06 11:14:05 +00:00
Sloth
5558692821 - Fixed "At the beginning of the end step, sacrifice CARDNAME." and friends. 2013-08-06 10:30:57 +00:00
swordshine
47db405dd5 - Fixed Tithe
- updated some scripts
- removed unused imports
2013-08-06 08:58:51 +00:00
swordshine
47faabd7f6 - Converted Strom world for multiplayer 2013-08-06 06:03:31 +00:00
swordshine
409dd8c250 - Added Volcano Hellion 2013-08-06 03:07:04 +00:00
spr
1b995b1c0d - CardPicturePanel now uses new FImagePanel instead of ScaledImagePanel (more details at http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=11218). 2013-08-06 00:38:42 +00:00
spr
1a48640042 - Fix : Two identical cards with one foiled. Picture preview does not display foil image when moving mouse pointer from the non-foil card. 2013-08-05 23:31:50 +00:00
Sloth
9d3a944033 - Fixed makeEvokeSpell messing up in Deck Editor. 2013-08-05 18:02:21 +00:00
swordshine
f1ea2abcc5 - Fixed Knowledge Pool 2013-08-05 13:36:16 +00:00
swordshine
108c0662ca - ChooseCardEffect for Sundering Titan should be mandatory 2013-08-05 13:05:49 +00:00
Chris
4ad15366ae - Added new card names to changes.txt. 2013-08-05 12:16:13 +00:00
swordshine
1c53168e12 - Converted keyword "At the beginning of your upkeep, CARDNAME deals " 2013-08-05 11:52:51 +00:00
swordshine
df5ded6050 - Converted "At the beginning of your upkeep, sacrifice CARDNAME unless you pay"
- Adding counters should be part of resolving of Cumulative upkeep
2013-08-05 11:41:47 +00:00
swordshine
52dfa47d6d - Converted the other two cards with "At the beginning of your upkeep, destroy CARDNAME" keyword to script 2013-08-05 11:19:31 +00:00
moomarc
2df22b5a42 - New Plane: Lair of the Ashen Idol 2013-08-05 11:19:19 +00:00
swordshine
a6a151f98f - Converted Cosmic Horror to script 2013-08-05 11:01:07 +00:00
swordshine
ec645d7f0e - Converted the triggered ability of Echo 2013-08-05 10:49:46 +00:00
moomarc
1d2666c598 - Added Wall of Caltrops 2013-08-05 10:16:06 +00:00
moomarc
85a9990f50 - Added Mangara's Equity 2013-08-05 08:34:17 +00:00
Sloth
6a681094f8 - Added Awesome Presence. 2013-08-05 08:15:53 +00:00
drdev
c8ee474025 Cleanup file header 2013-08-05 08:00:49 +00:00
drdev
8b4da360c1 Create FAbsolutePositioner and FScrollPanel for GUI toolbox
Improve layout of home screen menu panel
Allow scrolling the home screen menu using mouse wheel or arrow buttons
2013-08-05 07:59:48 +00:00
drdev
313ede1465 Add "Set window size" tool for Developer mode 2013-08-05 07:38:37 +00:00
Sloth
2bc8120186 - Improved ControlGainAi. 2013-08-04 18:53:52 +00:00
Sloth
068dce1b56 - Improved Evoke AI. 2013-08-04 18:29:33 +00:00
Sloth
7a110a80b5 - Fixed getDefinedSpellAbilities not using stackInstances. 2013-08-04 12:11:08 +00:00
Chris
a1da50a1dd - Added new card names to changes.txt. 2013-08-04 12:08:38 +00:00
Sloth
8bfe0746dd - Added Ayesha Tanaka. 2013-08-04 10:17:19 +00:00
Sloth
5e625dc08d - More use of playImmediately AI function. 2013-08-04 09:19:57 +00:00
Maxmtg
e16d885d30 renamed list of combatants that dealt 1st strike damage and added a comment for better understanding of code. 2013-08-04 07:48:49 +00:00
swordshine
369405e1f5 - Added Strionic Resonator 2013-08-04 06:34:24 +00:00
swordshine
d34e40ea29 - Plane: Added Jund
- Converted Maelstrom Nexus to script
2013-08-04 06:33:19 +00:00
swordshine
759a9bed7a - Converted Poisonous, Cascade
- Converted Celestial Mantle and Scalpelexis to script
2013-08-04 06:30:14 +00:00
swordshine
a7e0522cf9 - Converted Annihilator 2013-08-04 06:25:38 +00:00
swordshine
44f819e9be - Updated PlayEffect and related scripts for linked abilities 2013-08-04 06:24:48 +00:00
swordshine
f55566956e - Added Arcanum Wings and Grip of Chaos 2013-08-04 06:12:43 +00:00
swordshine
9d9ae41006 - Updated SkieraCube 2013-08-04 06:10:54 +00:00
swordshine
b19a6ae0d4 - Converted Frenzy Sliver, Fixed Sylvan Library 2013-08-04 06:06:45 +00:00
swordshine
d4996103c5 - Fixed RepeatEffect 2013-08-04 06:04:22 +00:00
Sloth
ef4ef398ed - More use of playImmediately AI function. 2013-08-03 21:21:35 +00:00
Sloth
82b6055a6d - More use of playImmediately AI function. 2013-08-03 20:45:15 +00:00
Sloth
994dfe1366 - The AI will now try to activate abilities of permanents about to be destroyed. 2013-08-03 18:40:11 +00:00
Chris
6e71b2f120 updated readme.txt 2013-08-03 13:08:41 +00:00
Sloth
26bea083af - Fixed Gorilla Berserkers. 2013-08-03 08:07:49 +00:00
drdev
800786f314 Prevent card zoomer getting stuck in some cases 2013-08-03 02:17:36 +00:00
Sol
9ff7adab79 - Fix AI logic paying life for Shocklands 2013-08-02 20:07:47 +00:00
Sloth
aa4c4acd6e - Improved playReusable AI. 2013-08-02 19:50:25 +00:00
Sol
345cece010 - Combat Damage now more accurate when considering gaining/losing first strike after first strike damage has been dealt. 2013-08-02 14:57:52 +00:00
Chris
ac00afd142 - Added new card names to changes.txt. 2013-08-02 12:52:51 +00:00
Chris
585e97538c - Cleared out the changes.txt file, now ready for new material. 2013-08-02 12:49:05 +00:00
moomarc
cfcd4e9404 - New Plane: Mount Keralia 2013-08-02 11:54:23 +00:00
Sloth
5865604e0f - Improved DamagePreventAi. 2013-08-02 10:20:28 +00:00
Agetian
398c8026df - A temporary measure to alleviate a severe issue with the game hanging in zoom mode on Linux operating systems when a mouse wheel is accidentally scrolled up while it's pressed to activate the "zoom-on-click" mode. 2013-08-02 08:45:31 +00:00
moomarc
594c22b9f9 - Added No Quarter 2013-08-02 08:09:06 +00:00
Agetian
b91d3a38f3 - Implemented a way to modify the card value if it's a foil.
- Code simplification related to setting a foil finish sprite.
2013-08-02 05:50:50 +00:00
Sloth
d4ddd8114c - Fixed Safe Haven. 2013-08-01 20:38:40 +00:00
Sloth
02bf1b283d - Fixed Dismantle. 2013-08-01 18:19:07 +00:00
Sloth
aa846eed7d - Fixed persist/undying triggering even when the dying has been replaced. 2013-08-01 18:16:23 +00:00
Sloth
487900ea58 - Fixed Hellrider. 2013-08-01 18:07:03 +00:00
Sloth
6d93668df5 - Added Wiitigo. 2013-08-01 12:39:48 +00:00
Sloth
4c73af527c - Fixed Grindstone. 2013-08-01 08:58:33 +00:00
spr
73f02b8c30 - Fix: refactoring introduced potential for NPE. 2013-07-31 22:08:09 +00:00
Sloth
1d51805181 - Updated two of the sliver decks with M14 slivers. 2013-07-31 21:22:31 +00:00
spr
b41dd6ff64 - A couple of Extract Method refactorings. Intention is to use in subsequent updates. 2013-07-31 18:21:10 +00:00
spr
e3f9bf17f6 - Fix : an image cache miss is generating a second redundant entry for the same image. 2013-07-31 18:11:47 +00:00
Chris
7a6ea2f2db [maven-release-plugin] prepare for next development iteration 2013-07-31 12:49:33 +00:00
Chris
ffe6d0b406 [maven-release-plugin] prepare release forge-1.4.5 2013-07-31 12:49:22 +00:00
Chris
3bff036d04 - Added new card names to changes.txt.
- Preparing the changes.txt file for the next beta build and release.
2013-07-31 12:39:07 +00:00
swordshine
b4ee92ea28 - Added Sea Troll
- Phenomenon: Reality Shaping
2013-07-31 11:18:52 +00:00
swordshine
e49147f5c2 - Plane: Eloren Wilds, Norn's Dominion, and Prahv
- Added Mishra's War Machine
2013-07-31 11:13:45 +00:00
swordshine
7aa3910911 - Added Circling Vultures and Mercadias Downfall 2013-07-31 11:06:56 +00:00
swordshine
16f0be2758 - Added Gilded Drake, Nefarious Lich and Rocket Launcher 2013-07-31 11:04:45 +00:00
swordshine
732bc9be73 - Converted Crumbling Sanctuary to script 2013-07-31 11:01:35 +00:00
Sloth
235abb58f4 - Fixed Legion Loyalist. 2013-07-31 07:33:59 +00:00
Maxmtg
5ff00b2c5c fix some possible NPEs (when combat = null) 2013-07-31 06:54:25 +00:00
Agetian
a0d37f7edd - Added the old style (pre-8ED) foil sprite sheet. 2013-07-31 05:29:50 +00:00
Agetian
04a0d0c04c - Foil cards will now correctly display in deck editors (with foil).
- Fixed the foil card display during the match.
2013-07-31 05:28:03 +00:00
drdev
49beec26c5 Fix right-click support for cards 2013-07-31 01:21:39 +00:00
Sloth
7686ef0d73 - Fixed a possible IndexOutOfBounds error in startGame. 2013-07-30 20:54:19 +00:00
Chris
c2d8fb9da2 different approach to 22705 fix. 2013-07-30 13:41:20 +00:00
Chris
9d6afb9f55 [maven-release-plugin] prepare for next development iteration 2013-07-30 12:55:38 +00:00
Chris
de805f68c5 [maven-release-plugin] prepare release forge-1.4.4 2013-07-30 12:55:29 +00:00
moomarc
79a1ed3d03 - small fix to my AILogic from previous commit 2013-07-30 12:45:30 +00:00
Chris
5a451977a4 - Preparing the changes.txt file for the next beta build and release. 2013-07-30 12:44:47 +00:00
Chris
50e482df9a Turn off Tests for ReadDraftRankings 2013-07-30 12:14:59 +00:00
moomarc
576697993e - Added Aegis of Honor 2013-07-30 12:02:09 +00:00
Sloth
7adc31890c - Fixed copying modal spells. 2013-07-30 11:31:55 +00:00
Sloth
afa42a53cd - Fixed tokens leaving the battlefield not unpairing. 2013-07-30 11:15:51 +00:00
asepetci
3cb2b09dd6 updated rankings.txt 2013-07-30 06:17:10 +00:00
drdev
35213d720e Minor update to CHANGES.txt 2013-07-30 01:37:58 +00:00
drdev
dd0b8b94fa Fix Continue and Restart in match screen 2013-07-30 01:15:37 +00:00
Chris
152587e5c4 - Added new card names to changes.txt. 2013-07-29 12:11:06 +00:00
moomarc
70c98f6cea - more changes to generated snake theme 2013-07-29 07:42:24 +00:00
moomarc
288c1d8867 - Added Mark of Sakiko
- added two new cards to the Snakes semi-random deck
2013-07-29 06:46:32 +00:00
drdev
a9ab0c7fc6 Fixed so, after dragging a pane tab to a new location, the pane it was in before selects its most recent remaining tab instead of being blank 2013-07-29 01:51:46 +00:00
Sloth
af89ddf40e - Fixed Vizkopa Confessor with X=0. 2013-07-28 22:31:22 +00:00
Maxmtg
57c7e32361 fix to issue 721 2013-07-28 22:04:29 +00:00
drdev
00779c550c Support card zoom while holding middle mouse button down or holding left and right mouse button down at same time
Fix so cards are "selected" on mouse up instead of mouse down, allowing things like drag drop and zoom to work if the card is currently actionable
2013-07-28 21:30:19 +00:00
Sloth
6f128f61e1 - Improved AttachAI for Fortifications. 2013-07-28 15:14:42 +00:00
Sloth
9ad1c5d2e7 - Improved AI dealing with Immobilizing Ink. 2013-07-28 15:07:06 +00:00
moomarc
d06d797ab1 - Added Sakiko, Mother of Summer 2013-07-28 13:14:02 +00:00
moomarc
14ae68b38c - Added Shizuko, Caller of Autumn 2013-07-28 12:08:14 +00:00
moomarc
85a31e7f04 - small fixes to CHANGES.txt (Vengeful Archon wasn't new and Edge of Malakol in correct section) 2013-07-28 11:56:25 +00:00
moomarc
003081508c - Added Sakura-Tribe Springcaller 2013-07-28 11:51:09 +00:00
moomarc
e62d8e2bdc - Added Journeyman skin to default release skins 2013-07-28 11:38:01 +00:00
Sloth
2cacb05e66 - Fixed prompt of handleLegendRule. 2013-07-28 07:39:18 +00:00
Sloth
8c4f6650c2 - Fixed trigger of Deep-Sea Kraken. 2013-07-28 07:37:39 +00:00
drdev
5da85b079a Save and restore selected tab for each pane
Avoid refreshing entire Deck Editor when opening a deck
2013-07-28 00:50:38 +00:00
drdev
0c9a29000e Added double click support to All Decks list and fixed hover bug 2013-07-27 22:50:02 +00:00
drdev
412b7501e5 Added right-click support for filter buttons
Fixed bugs with handling button click events
2013-07-27 22:06:34 +00:00
Sloth
87ffcf737f - Fixed AddCounter costs with amount of 0. 2013-07-26 05:57:38 +00:00
Chris
10a83ed994 - Added new card names to changes.txt. 2013-07-25 11:40:29 +00:00
Sloth
b9da0fa6bd - Made Vengeful Archon AI safe (but not playable). 2013-07-24 20:36:46 +00:00
Sloth
b2957a56a8 - Flying for Scourge of Valkas. 2013-07-24 16:13:51 +00:00
Maxmtg
e54cef963b improve ai-vs-ai cli: number of games can be specified, removed dialog with cards ai cannot play 2013-07-24 05:33:50 +00:00
Chris
410e562888 - Added a fluff piece to the changes.txt file. 2013-07-23 19:29:33 +00:00
Chris
58a2cb6258 - Added new card names to changes.txt. 2013-07-23 11:33:43 +00:00
swordshine
c658c937a0 - Added Frankenstein's Monster 2013-07-23 10:31:49 +00:00
swordshine
217f78a5e8 - Plane: added Celestine Reef 2013-07-23 07:57:17 +00:00
swordshine
8d3c817195 - Plane: added Agyrem 2013-07-23 06:13:05 +00:00
spr
9ce5fc4f58 - Added a Card Zoomer! 2013-07-23 03:29:34 +00:00
swordshine
b8eff3db00 - Added Edge of Malacol 2013-07-23 00:53:28 +00:00
Chris
cbe7be7d20 - Added a fluff piece to the changes.txt file. 2013-07-22 21:53:29 +00:00
Sloth
021f482aea - AI will now play lands exiled with Uba Mask. 2013-07-22 21:41:29 +00:00
Maxmtg
24b371d58a ai vs ai games can be run from command line interface 2013-07-22 21:03:04 +00:00
swordshine
010edda2cd - Fixed Far // Away 2013-07-22 11:47:29 +00:00
Chris
a2ecdabe8d - Added new card names to changes.txt. 2013-07-22 11:24:47 +00:00
swordshine
deda7f13d1 - Another fix for LKI 2013-07-22 10:56:33 +00:00
swordshine
1152f3f490 - Fixed several cards with sacrifice effect not using LKI 2013-07-22 10:46:48 +00:00
swordshine
89151422ba - Added Planar Overlay 2013-07-22 09:55:55 +00:00
swordshine
0d6ae53860 - Fixed Hoarding Dragon 2013-07-22 09:22:04 +00:00
swordshine
55d0eb904d - Fixed Sporogenesis by squee1968
- Added Twist Allegiance
2013-07-22 07:49:57 +00:00
Maxmtg
ec1f2aa35a Lobby and NetServer moved out of FControl to allow their usage without Gui loaded (and FControl instantiated) 2013-07-22 06:40:36 +00:00
swordshine
f3a6b9f536 - Added Timesifter 2013-07-22 05:15:54 +00:00
swordshine
4f60bcc16d - Added Stronghold Gambit 2013-07-22 01:52:23 +00:00
swordshine
2255ebd533 - Added Steam Vines 2013-07-22 00:47:37 +00:00
swordshine
1ebf2f5003 - Added Suffocation 2013-07-22 00:35:40 +00:00
Maxmtg
2a55d1ea03 card detail panel will not display card id for cards not belonging to any game (id<=0) 2013-07-21 22:06:42 +00:00
Maxmtg
4d9c988059 card id is assigned externally and cannot change.
card ids have to be unique within game to support multiple simultaneous games
2013-07-21 21:31:21 +00:00
Sloth
ef0225c1bd - Fixed Totem Armor crashes. 2013-07-21 17:02:21 +00:00
Agetian
633fec0094 - Terminology correction in ChooseSourceEffect (according to phrasing in the comprehensive rules). 2013-07-21 13:33:39 +00:00
Agetian
d5c9a9c34a - Implemented a part of the rule 119.7: a source can be a face-up card in the command zone. 2013-07-21 11:55:25 +00:00
swordshine
a547bbc815 - Added Rogue Skycaptain 2013-07-21 11:17:03 +00:00
Chris
2af5a26ca3 - Added new card names to changes.txt. 2013-07-21 10:53:48 +00:00
Sloth
24b61319ca - Cleanup. 2013-07-21 10:45:47 +00:00
Sloth
560b18ab14 - Fixed a bug in getAllBlockers (don't know whether it's relevant). 2013-07-21 10:32:34 +00:00
Sloth
2bee73487a - Little fix in executeRampageAbility. 2013-07-21 10:29:35 +00:00
Sloth
767df4ba4f - Fixed a bug in setLabelLayout (don't know whether it's relevant). 2013-07-21 10:20:40 +00:00
swordshine
93522443a4 - Added Sporogenesis 2013-07-21 06:33:11 +00:00
swordshine
af20f7b37c - Fixed Elite Arcanist and Unexpected Results 2013-07-21 05:58:52 +00:00
spr
795c0c2673 - Fix : NullPointer exception if AI and/or Player deck not specified in Sanctioned Format: Constructed view. [#688] 2013-07-21 03:29:10 +00:00
spr
b97e89754b - New turn log entry is emphasized.
- Updated FSkin.java to return default font at a default size instead of point size 1.
2013-07-21 01:25:31 +00:00
Sloth
80ad9ec410 - Fixed Terastodon and friends when not targeting anything. 2013-07-20 21:37:11 +00:00
Maxmtg
ba9fb93a7b removed currentGame from Match (in theory parallel matches are now possible, such as ai vs ai matches started in different threads)
Match no longer has last game outcome.
Win/Lose screens recieve just played game as a parameter to initialize and perform ante manipulations
2013-07-20 21:16:21 +00:00
Sloth
cb461a2304 - Fixed AI not always choosing targets for optional triggers. 2013-07-20 21:01:57 +00:00
Maxmtg
b4f6dbdeb6 added a method to launch game by a single call to FControl (removed duplicate code from controllers specific to game modes) 2013-07-20 20:37:25 +00:00
Sloth
ab9092d53e - Improved AnimateAi. 2013-07-20 20:23:26 +00:00
Maxmtg
b741c594e2 removed unused function 2013-07-20 20:15:06 +00:00
Chris
01973b0712 - Temporary disabling of the first two card tests which caused the build failure. 2013-07-20 20:12:48 +00:00
Sloth
00ffcc50d8 - Fixed LTB triggers on tokens triggering twice. 2013-07-20 14:29:19 +00:00
Chris
e1503691f9 - Added new card names to changes.txt. 2013-07-20 11:32:53 +00:00
Sloth
f8ecb5298f - Improved AI using Vithian Stinger. 2013-07-20 10:52:26 +00:00
Sloth
f83c4a36b5 - Improved AI anticipating activated First Strike abilities. 2013-07-20 10:25:20 +00:00
Sloth
f839c1c41c - Implemented rule 303.4h for auras. 2013-07-20 06:47:48 +00:00
Sloth
f94b44b65f - Prevent crash when AI is forced to play Angel of Salvation. 2013-07-19 22:12:26 +00:00
Sloth
928ac60b42 - Updated 3 quest decks with M14 cards. 2013-07-19 20:54:26 +00:00
Sloth
9bf9c167a7 - Improved AI using Tribute to the Wild. 2013-07-19 18:52:12 +00:00
moomarc
fd63bda12a - Added scheme: Drench the Soil in Their Blood
- Archenemy schemes will only be played on pre-combat main phases.
2013-07-19 15:23:48 +00:00
moomarc
bde583d381 - Moved evaluateBoardState to ComputerUtil from ComputerUtilCard 2013-07-19 15:07:42 +00:00
moomarc
6a6050dea2 - Added scheme: Choose Your Champion and supporting AI logic.
- Added a basic board state evaluation that returns the player from a list with the best rating. Could probably be improved with better weightings.
2013-07-19 14:51:49 +00:00
Chris
355ab88ba9 - Added new card names to changes.txt. 2013-07-19 12:17:12 +00:00
moomarc
adc22f97ff - Added Cycle of Life 2013-07-19 11:32:26 +00:00
swordshine
fdd53c2668 - Fixed Fact or Fiction 2013-07-19 08:43:32 +00:00
moomarc
37e3dac97f - Updated my details in the pom.xml 2013-07-19 06:43:52 +00:00
Sloth
ac99a905ec - Little addition for Shape of the Wiitigo. 2013-07-19 05:43:37 +00:00
Maxmtg
5b91a68b2f Moved quest game initialization to fcontrol, since quest was a bad dependence from game
separated createGame and tartGame to make Fcontrol.attach optinal and called from UI code that starts a game
2013-07-18 21:37:02 +00:00
Sloth
5b2ceef0aa - Added Shape of the Wiitigo. 2013-07-18 18:58:58 +00:00
spr
2c54c811d5 - Fix: theme combo displays "Dark Ascension" when current theme is "Default". [#716] 2013-07-18 16:36:40 +00:00
asepetci
55a8d9a552 updated rankings.txt 2013-07-18 12:49:34 +00:00
Chris
93227be07e - Added new card names to changes.txt. 2013-07-18 10:14:38 +00:00
moomarc
42adaacb04 - Added Thought Dissector 2013-07-18 09:19:06 +00:00
moomarc
0b6cbb0c43 - Added Spatial Binding
- fixed state based effects handling auras and equipment when they would be indirectly phased out but can't
2013-07-18 08:20:46 +00:00
moomarc
37568e0410 - Added Tide of War
- added BlockersDeclares trigger
2013-07-18 06:56:55 +00:00
Sol
eba08dd086 - Adding Fat Pack info for M14 2013-07-18 01:53:05 +00:00
Sol
3472ce4675 - Redraw after Morph Up 2013-07-18 00:58:29 +00:00
spr
0465095bbc - New Themed ComboBox setting to enable/disable themed combos addressing coloring clashes in Mac OSX causing readability problems [#715]. 2013-07-17 22:45:44 +00:00
Sloth
6134d2585c - Fixed trigger of AI's Sphinx-Bone Wand not being removed from stack. 2013-07-17 21:00:42 +00:00
Sloth
344643ffdd - The Trigger of the harbingers is now optional. 2013-07-17 20:11:55 +00:00
Sloth
4bfc8bb117 - Populate is now mandatory. 2013-07-17 20:03:27 +00:00
Chris
cce1d2f361 - Added new card names to changes.txt. 2013-07-17 11:58:34 +00:00
moomarc
ab68c32549 - Fixed Reverent Silence (and other "each opponent gains life" cost) 2013-07-17 10:53:04 +00:00
moomarc
0c6275777e - Added Hallow 2013-07-17 10:24:23 +00:00
Sloth
0ae4cd25b6 - Implemented multiplayer rule 802.4a: "A player can block only creatures attacking him or a planeswalker he controls". 2013-07-17 10:08:46 +00:00
Sloth
8d030f3fac - Added a copy of Tsabo's Assassin to the quest deck Hermes Conrad 2. 2013-07-17 09:27:36 +00:00
moomarc
e6df31e338 - Added Mindbender Spores 2013-07-17 06:44:38 +00:00
Agetian
6a09f78fc5 - Fixed a bug related to triggers firing when counters are removed. 2013-07-17 05:28:00 +00:00
Sol
57844e8c7c - Add references for Extort 2013-07-17 02:50:31 +00:00
Sol
845d67db52 - Fixing Ninjutsu cards not setting unblocked that was broken in r22261
Most hilarious NPE at a line commented with "// this is called after declare blockers, no worries 'bout nulls in isBlocked"
2013-07-17 00:39:22 +00:00
Sol
4f5de217e7 - Fix issue with Quest Wins Per Booster being set to 0 2013-07-17 00:13:20 +00:00
Sol
39d18a48c8 - Updating Oracle N-Z 2013-07-16 23:23:01 +00:00
Sol
0bea980aa7 - Updating oracle texts A-M 2013-07-16 23:22:20 +00:00
moomarc
b0e09f8cff - Added Samite Blessing 2013-07-16 17:16:11 +00:00
spr
299ab754b3 - New Visual Themes setting to hide or show background image on match screen. Default show. 2013-07-15 19:19:01 +00:00
spr
cada68dee5 - Converted AI Profile list to themed combo and moved to top of GamePlay section. 2013-07-15 19:04:38 +00:00
spr
70c2d18abe - Converted Skins list to themed combo and added to new Visual Themes section. 2013-07-15 18:56:39 +00:00
spr
dd34b9bf0f - Refactoring. Moved Dev Mode and Log Verbosity into Advanced Settings section. Refactored FComboBoxPanel. 2013-07-15 18:46:04 +00:00
moomarc
4c52a82e85 Fixed Faithful Squire//Kaiso, Memory of Loyalty (flip side was missing flying) 2013-07-15 18:08:08 +00:00
Chris
99f30eb07e - Added new card names to changes.txt. 2013-07-15 11:38:01 +00:00
spr
7f67bfb2df - All reset buttons now appear under Troubleshooting section.
- Increased visibility of Troubleshooting section by moving to the top of the preferences screen.
- Added confirmation dialogs to all reset buttons.
2013-07-15 09:18:31 +00:00
spr
4a2c0a2536 - Fix: UI_SKIN default value should be "Default" instead of "default". 2013-07-15 09:00:29 +00:00
swordshine
019685ef9b - Fixed Debuff Effect 2013-07-15 05:54:45 +00:00
swordshine
33b29ca55d - Fixed ChangeZoneAll effect remember things twice (Winds of Charge) 2013-07-15 05:45:33 +00:00
Sol
e9d962388d - Bugfix for Finest Hour: Combats this turn needs to be incremented before triggers are run. 2013-07-15 03:59:05 +00:00
Sol
913a6ec653 - ChooseCard will now use the "order" dialog instead of multiple popups if you need to choose more than one Card
- Convert Phyrexian Dreadnought and Sutured Ghoul to script
2013-07-15 02:56:54 +00:00
swordshine
d2c2138be6 - Added Kudzu 2013-07-15 00:18:51 +00:00
Chris
4bb0b15adc - Added new card names to changes.txt. 2013-07-14 12:02:05 +00:00
swordshine
6aa57212a9 - Vanguard: added Enigma Sphinx Avatar 2013-07-14 09:38:32 +00:00
swordshine
5a30bc849e - Added Autumn's Veil 2013-07-14 07:00:17 +00:00
swordshine
995c5705e5 - Added Nettlevine Blight 2013-07-14 05:54:57 +00:00
swordshine
c580b15643 - Fixed Mesmeric Sliver 2013-07-14 05:14:20 +00:00
swordshine
9a97bc6824 - Fixed Deadeye Navigator 2013-07-14 05:04:40 +00:00
Chris
9c58d20320 - Cleared out the changes.txt file, now ready for new material. 2013-07-13 12:40:03 +00:00
swordshine
210e06fd62 - the new Gaea's Touch 2013-07-13 12:26:06 +00:00
Sloth
cb085223b3 - Fixed Mindstab Thrull crash. 2013-07-13 12:06:18 +00:00
swordshine
b8954ef27b - Fixed Golem Artisan and Lunar Avenger 2013-07-13 06:06:45 +00:00
spr
b7ca99dd55 - New Game Log Verbosity setting. 2013-07-13 02:12:25 +00:00
swordshine
191783d30c - Fixed Jodah's Avenger 2013-07-13 01:18:51 +00:00
Sloth
0ba70bb4f7 - Fixed Guardian of the Ages. 2013-07-12 20:31:07 +00:00
Sloth
277f63609c - Fixed Thorncaster Sliver. 2013-07-12 20:27:49 +00:00
Sloth
cd01b7ba6b - Fixed Young Pyromancer. 2013-07-12 20:23:44 +00:00
Sloth
18a25447a6 - Moved Unearth replacement effect to GameAction.changeZone so it will work correctly with triggers.
- "If CARDNAME would be put into a graveyard from anywhere, reveal CARDNAME and shuffle it into its owner's library instead." will now be turned into a proper replacement effect with a macro.
2013-07-12 20:22:39 +00:00
Sol
93468a849c - Converted Transmute Artifact to script 2013-07-12 18:42:22 +00:00
Chris
bc864fa71e [maven-release-plugin] prepare for next development iteration 2013-07-12 15:11:40 +00:00
Chris
63e46dcf12 [maven-release-plugin] prepare release forge-1.4.3 2013-07-12 15:11:29 +00:00
Sloth
3ac1ddb51e - Reverted r22549 and used another fix. 2013-07-12 13:39:21 +00:00
Chris
18d3b1ffa8 - Preparing the changes.txt file for the next beta build and release. 2013-07-12 12:47:12 +00:00
Sloth
3c713fb362 - Fixed Karn Liberated. 2013-07-12 11:25:09 +00:00
swordshine
c74a577030 - Legend rule for Brothers Yamazaki 2013-07-12 09:49:13 +00:00
Sloth
a849c03651 - Fix for last commit. 2013-07-12 09:42:41 +00:00
Sloth
bf8be3096d - Moved CardsAddedThisTurn registration from Zone.add to changeZone. 2013-07-12 09:32:38 +00:00
jsv
34a5375a21 Fixed a typo in Goblin Replica. 2013-07-12 06:19:56 +00:00
swordshine
2303f414bb - Fixed Bubbling Cauldron
- Fixed Rally the Righteous
- Card Panel: keyword "Unblockable" changed to "Can't be blocked."
2013-07-12 00:31:56 +00:00
spr
8331e86624 - Modified setting. "Text/Mana Overlay" replaced with individual toggle setting for Card Name, P/T and Mana Cost overlays. 2013-07-11 23:00:01 +00:00
spr
c591902722 - Addresses issue 0000659 concerning the sidebar menu options extending past bottom of screen. Added new Compact Menu setting that will prevent more than one menu group from being open at a time which should alleviate this problem. 2013-07-11 15:30:30 +00:00
Chris
36a478d78d - Added new card names to changes.txt. 2013-07-11 11:15:25 +00:00
jsv
6f278cead4 Fixed a typo in Legolas deck description. 2013-07-11 11:03:45 +00:00
spr
6fe7063664 - Fix: Opening Deck Editor crashes Forge if you have previously dragged Draw Order to its own tab and restarted Forge. [ISSUE 710]. 2013-07-11 07:12:22 +00:00
Maxmtg
c1f903afcd Using Serum Powder from input mulligan runs exile-draw in a right thread 2013-07-11 06:26:06 +00:00
swordshine
2344bf696e - Added Colossal Whale and Banisher Priest
- Fixed targeting of Postmortem Lunge
- Fixed Mask of Memory
2013-07-11 00:20:58 +00:00
Maxmtg
ba93f2a431 AI: changed many calls to accept Collection<T> instead of List<T>
GameAction - legend rule and planeswalker rule are updated to match changes introduced with "Magic 2014 Core Set"
InputSelectCardsFromList also accepts any Collection<T>, not just List<T>
PlayerControllerHuman - chooseSingleCardForEffect tries to use InputSelectCardsFromList when all cards are in Battlefield or own hand
2013-07-10 22:07:08 +00:00
Sloth
15b3698619 - Updated some SVars of M14 cards. 2013-07-10 20:06:52 +00:00
Sloth
10060a66f8 - Updated some SVars of M14 cards. 2013-07-10 18:47:40 +00:00
Maxmtg
3ccc53a63c sends events when damage assigned changes 2013-07-10 13:05:23 +00:00
Maxmtg
cd120bcad4 Added redraw for times when AI declares attackers and combat ends to show/hide combat icons
"exalted" ability inlined (because its routine became simplier after it had been replaced with script)
2013-07-10 12:22:26 +00:00
Chris
d282f07720 - Added a fluff piece to the changes.txt file. 2013-07-10 11:21:22 +00:00
Maxmtg
1d66df366f sword icon removed from creature that is un-declared as attacked from InputAttack 2013-07-10 11:12:40 +00:00
Sloth
3a0f0cdfa8 - Updated the quest deck Bela Lugosi 3 (with input from Nordos). 2013-07-10 06:17:24 +00:00
Sloth
ed291cfe4d - Restored AI using the DiscardMeByOpp SVar. 2013-07-10 06:13:48 +00:00
swordshine
eec5a085e8 - Fixed Marauding Maulhorn
- Updated SkieraCube
2013-07-10 05:36:37 +00:00
Sol
44ebba25e5 - Fix Corpse Hauler targeting 2013-07-10 01:57:52 +00:00
Sloth
eb484df801 - Added M14 to some lists. 2013-07-09 18:57:50 +00:00
Maxmtg
a3b84f0876 m14 set description file 2013-07-09 18:54:55 +00:00
Sloth
3aaa7345f6 - Improved canPlayAI() of LevelUp abilities. 2013-07-09 18:45:13 +00:00
Sloth
7559f7c2f9 - Merged M14 branch into trunk. 2013-07-09 17:59:16 +00:00
Maxmtg
e48e7aa544 redraw battlefield when mana payment is cancelled 2013-07-09 16:04:15 +00:00
swordshine
55a71ea46a - Fixed Dance, Pathetic Marionette 2013-07-09 13:13:56 +00:00
Agetian
6879729b01 - Implemented Archenemy rules 904.5 and 904.6 (the Archenemy has 40 life and always takes the first turn of the game). 2013-07-09 11:50:00 +00:00
Chris
ad5f9232f7 - Added new card names to changes.txt. 2013-07-09 11:26:06 +00:00
Sloth
45c44fd936 - Fixed Sleeper Agent. 2013-07-09 08:20:39 +00:00
swordshine
f7cc0dbd00 - Fixed searching a card in library and put it on the top 2013-07-09 07:40:45 +00:00
Maxmtg
0bf2cc8442 playSpellAbilityNoStack - added parameter 'mayChooseNewTargets' - it is passed to HumanPlaySa method and is a key to call doTrigger on AI's side 2013-07-09 05:55:43 +00:00
swordshine
23c8590123 - M14: Added Artificer's Hex 2013-07-09 04:06:12 +00:00
Sol
5ad15655a9 - Convert Master of the Wild Hunt to script 2013-07-09 01:28:52 +00:00
Maxmtg
59249a7155 Prerequisites to launch Forge without UI. (used by ai-vs-ai CLI simulation and by dedicated server mode - when it's done) 2013-07-08 23:05:40 +00:00
Maxmtg
7f545935b4 playNoStack called via playercontroller 2013-07-08 22:37:45 +00:00
Maxmtg
fe9001a550 moved the possible options evaluation down the call stack 2013-07-08 22:23:41 +00:00
Maxmtg
313bed630a moved choice of ability modes to PlayerController - got rid of both: bad imports in CharmEffect (gui classes) and isHuman() call 2013-07-08 22:14:59 +00:00
Sloth
d030b09c51 - moveToLibrary now redirects to changeZone. 2013-07-08 20:52:04 +00:00
Sloth
454557a468 - Improved Charm AI for Tormentor Exarch. 2013-07-08 20:43:36 +00:00
Sloth
1c7a1e3d8f - Improved AI using Tormentor Exarch. 2013-07-08 19:58:17 +00:00
Sloth
e3e937354b - Fixed chooseOptionsAi causing NPE's. 2013-07-08 19:38:55 +00:00
Sloth
83936a924e - Fixed Effects not leaving Command zone. 2013-07-08 19:25:51 +00:00
Maxmtg
0c74043668 transformed cards generate gameeventcardchanged 2013-07-08 17:41:20 +00:00
swordshine
4a7cee412c - Fixed Quicken 2013-07-08 14:47:23 +00:00
Sloth
b7972342dc - Fixed setTriggeringObjects of AttackerBlockedTrigger. 2013-07-08 14:06:09 +00:00
swordshine
7e0f5e1401 - M14: Added Tenacious Dead and Xathrid Necromance 2013-07-08 14:00:21 +00:00
swordshine
d55ced465d - M14: Added Dismiss into Dream, Elite Arcanist, Tidebinder Mage, Zephyr Charge 2013-07-08 12:29:50 +00:00
Chris
8d2bf7571a - Added new card names to changes.txt. 2013-07-08 12:29:05 +00:00
moomarc
764da32cba - Fixed Polluted Bonds for multiplayer games 2013-07-08 11:54:07 +00:00
jsv
1d6a015939 Fix Tolarian Entrancer's script 2013-07-08 10:18:03 +00:00
swordshine
68a9f55f53 - M14: Added Voracious Wurm 2013-07-08 07:24:32 +00:00
swordshine
e638e7f2c9 - M14: Added Angelic Accord 2013-07-08 07:15:04 +00:00
swordshine
d3aef40bb3 - Fixed a bug when searching a card in the library and put it on the top 2013-07-08 01:05:11 +00:00
Sol
e081e66a13 - Only reset quest new cards at the beginning of the match, not the end of each game 2013-07-08 00:15:54 +00:00
Sol
7f055ddb4b - Adding some references to two scripts 2013-07-08 00:15:04 +00:00
Maxmtg
79f8704a00 PhaseHandler: added a missing check for gameover state between SBA and givePriorityToPlayer (handles draws properly)
Zone, GameAction: inlined some single-use very short functions
2013-07-07 22:58:02 +00:00
Maxmtg
df3b938ed0 changed order or actions performed on declare blockers step: declare, pay extra costs, fire event, then fire triggers
EventVisualizer.java will be able to play sounds when ai is blocking
2013-07-07 22:09:20 +00:00
Sloth
cf27269f08 - Added SVar:RemAIDeck:True to cards with banding. 2013-07-07 19:41:42 +00:00
Maxmtg
4d0b02e049 BlockersDeclaredEvent causes redraw of all blocker cards (they'll get icons this way) 2013-07-07 19:33:36 +00:00
Maxmtg
f8bb07ec35 Zone.add(Card) is now a shortcut to zone.add(card, Integer). This allows to remove a couple of if's in GameAction 2013-07-07 19:32:17 +00:00
Sloth
601d789662 - Implemented official rules for tokens leaving the battlefield (fixes Cloudshift). 2013-07-07 16:47:49 +00:00
Sloth
54b7e8f1b1 - Fixed two trigger descriptions. 2013-07-07 15:30:29 +00:00
Sloth
33df962e70 - Fixed triggers with targets and a cost not being removed from the stack when the cost is not payed. 2013-07-07 15:23:23 +00:00
Maxmtg
ab894eeca3 newly declared attackers and defenders are highlighted at once 2013-07-07 14:45:06 +00:00
Maxmtg
bf4e21933c Added another event bus to MatchUI. Attached sound system to this bus
Attack/Block inputs use this bus to request sounds playback and UI updates
SoundSystem - remove mostly duplicated play/playSync calls - now there's one call with a single mandatory parameter.
corrected tabs into spaces somewhere in sound system (why can't Agetian just tune up his IDE?)
2013-07-07 14:26:09 +00:00
Chris
0ea254e94c - Added new card names to changes.txt. 2013-07-07 13:11:53 +00:00
Sloth
94ae69813f - Improved AI using mana sources with Combo Any. 2013-07-07 12:51:03 +00:00
Maxmtg
f6782893e7 GameAction - using a couple of variables to cache toBattlefield, fromBattlefield, extracted method to fix attachments 2013-07-07 12:44:50 +00:00
Maxmtg
54ba881dd2 remove unused import 2013-07-07 12:31:58 +00:00
Sloth
2cd7cece85 - First steps towards AI canceling payment of a SpellAbility when it fails to pay (instead of throwing an error). 2013-07-07 11:56:49 +00:00
Sloth
74e2bcfc37 - More NPE prevention of cards without sets in preparePlayerLibrary. 2013-07-07 11:37:11 +00:00
Maxmtg
d3f5758df8 Fixing typo 2013-07-07 11:08:29 +00:00
swordshine
da052c02e2 - Fixed mana ability (Deathrite Shaman's first ability is not a mana ability) 2013-07-07 09:55:35 +00:00
Agetian
f063f52981 - Properly fixing Discard effect for cards like Last Rites. 2013-07-07 07:53:20 +00:00
swordshine
2646d7e656 - M14: Added Pyromancer's Gauntlet 2013-07-07 07:43:00 +00:00
Agetian
0e22f73d11 - Trying to fix a bug with the DiscardEffect that won't function properly for cards asking to discard more than 1 card (e.g. Last Rites). NOTE: This is an experimental hack, please review line 395 in PlayerControllerHuman, not sure how to properly resolve that part. 2013-07-07 06:28:37 +00:00
swordshine
2d7ada5106 - Fixed a bug that tokens don't move to library 2013-07-07 06:03:56 +00:00
Maxmtg
c8524485bb PlayerController.playSaNoStack - removed obvious parameter (player) 2013-07-06 23:28:37 +00:00
Maxmtg
191a8e4e43 This restores Valacut - Scapeshift combo
Have to figure out how this influences on OpeningHandEffects
2013-07-06 23:16:34 +00:00
Maxmtg
4f3e058d64 Game event log also reports elements from call trace 2013-07-06 22:32:26 +00:00
Maxmtg
da33b4f406 Witch-maw nephilim: trigger is unconditional, the ability is checking for conditions instead 2013-07-06 21:29:55 +00:00
Sloth
d5851fc4ed - Fix for Soul's Fire. 2013-07-06 20:56:49 +00:00
Sloth
de9adadf04 - Another fix for Deadshot. 2013-07-06 20:54:06 +00:00
Sloth
08f1ba8186 - Fixed more cards calculating with LKI values. 2013-07-06 20:45:41 +00:00
Sloth
135239eabe - Fixed Deadshot. 2013-07-06 20:11:55 +00:00
Maxmtg
ec838bb8a1 reverted checkstatic abilities after resolving a subability 2013-07-06 19:38:47 +00:00
Chris
52bc9b6326 - Added new card names to changes.txt. 2013-07-06 12:50:46 +00:00
Maxmtg
e7eff63585 added call to checkStaticAbilities to changeZone - this will take into consideration creatures' static abilities before firing ETB trigger.
removed same calls where they would be duplicate (player.playLand, AbilityUtils.resolveSubAbilities)
2013-07-06 09:22:15 +00:00
Agetian
21222511dd - Added proper AI battlefield conditions for Akoum.
- Minor comment modification.
2013-07-06 09:00:26 +00:00
Agetian
ffb7924585 - Code simplification in RollPlanarDiceAi. 2013-07-06 05:24:26 +00:00
Maxmtg
990f7ab9dc removed some excessive calls to checkStaticAbilities. (keep in mind they are always checked on priority along with state-based actions) 2013-07-05 22:55:23 +00:00
Maxmtg
30f85737c8 Rampage turned into api-based scripted effect
scripted witch_maw_nephilim.txt 2nd ability and removed hardcode
added some syntax sugar in TriggerType.java
2013-07-05 22:06:23 +00:00
Sloth
f1f196585d - Fixed Blood Tyrant. 2013-07-05 21:38:19 +00:00
Sloth
7bbeed304f - Fixed a bug in canPlayAI of ClashAI. 2013-07-05 21:36:12 +00:00
Sloth
bdbe7b25eb - Fixed Vendetta and Devour in Shadow calculating LKI toughness. 2013-07-05 21:32:04 +00:00
Maxmtg
d6e3576203 Alphabetically sorted trigger types 2013-07-05 20:56:04 +00:00
Maxmtg
c0411273a9 Flanking - converted to script (to use Pump API and it's ui updates already set up)
checkStateEffects - removed many calls leaving only the ones clearly stated by rules, in most cases calls replaced by checkStaticAbilities
in checkStateEffects - extracted some method performing certain state actions
2013-07-05 20:40:53 +00:00
Maxmtg
789cae29eb Umbra stalker - converted to script
inlined some chroma-related routines, and related to card list
2013-07-05 19:01:39 +00:00
Agetian
d668d9c3bb - Added some conditions to the AI for rolling a planar die.
- Some of the planes will now be used more intelligently by the AI depending on battlefield conditions.
- Some AI hint fixes for planes.
2013-07-05 18:49:47 +00:00
Maxmtg
4b8effe250 Added exceptions to constructor of PaperCard to prevent creation of illegal instances without set 2013-07-05 18:27:18 +00:00
Maxmtg
896a9a0a23 fix NPE happening at CHand init 2013-07-05 18:23:47 +00:00
Sloth
a28929bc36 - Fixed AI dodging the payment of AlternateAdditionalCost. 2013-07-05 18:13:53 +00:00
Sloth
59606709a6 - Fixed cards without set causing NPE's when foiled. 2013-07-05 18:05:45 +00:00
Sloth
5398d53408 - Added AI support for Fight triggers (Gruul Ragebeast). 2013-07-05 16:25:17 +00:00
Maxmtg
31e272711f Static abilities update: excludes from event cards in hidden zones like library (that won't be drawn anyway)
currentZone field added to card, it's a shortcut used to avoid enumeration of cards in all zones to find the requested one
2013-07-05 16:23:38 +00:00
Maxmtg
b12abc4533 Events are fired when static abilities recalculate 2013-07-05 15:06:15 +00:00
Maxmtg
628430f0e9 Bushido also uses cardscript with API = pump 2013-07-05 13:30:04 +00:00
Maxmtg
1f3501d8de Log_events moved to Game class (as they are submitted there)
Pump and PumpAll effects now fire update card stats event - for both start of effect and end of it.
Exalted effect - refactored to use scripted ability (the script is hardcoded)
toString routines for some events
2013-07-05 13:07:54 +00:00
Agetian
7e3858ef31 - Added a way to deprioritize the AI planar die roll compared to casting spells for mana.
- Made the AI roll the die for some of the planes multiple times (but only after casting spells).
- Some other optimizations and modifications of the plane AI hints.
2013-07-05 12:59:51 +00:00
jsv
b8613c5cdf Fixed Imperiosaur's cost restriction. 2013-07-05 06:37:18 +00:00
Agetian
8b8bb25717 - Added info about the Planechase AI extension to the list of changes. 2013-07-05 05:34:37 +00:00
Agetian
3871508385 - Fixed the AI hint for Grixis. 2013-07-05 05:23:06 +00:00
Agetian
32a5f4389b - Consolidating AI hints for planes in Planechase into a single "AIRollPlanarDieParams" parameter.
- Adding a foundation for the condition specifications in AI hints for planes.
- Code simplification in RollPlanarDiceAi.
2013-07-05 05:20:40 +00:00
Maxmtg
fadaedc382 excessive calls to SBE check removed 2013-07-05 05:19:14 +00:00
Agetian
1772faae3f - Restructured the logic of AI planar die rolls a little bit.
- Added a way to specify the minimum turn in which it is viable to start rolling a planar die for a particular plane. Added this setting to some planes which require a bit more context on the battlefield before their Chaos rolls start to make sense.
- Added an AI profile variable specifying the minimum turn in which the AI will start rolling the planar die in general unless the plane has an override specified (default AI - on its second turn, reckless AI - on its first turn).
2013-07-04 18:05:07 +00:00
Sloth
37d4245363 - Fixed Soul's Fire. 2013-07-04 16:41:04 +00:00
Sloth
2e8bb882eb - Fixed AnimateAi. 2013-07-04 16:09:15 +00:00
Agetian
73834a4455 - Added an extra break to the planar die AI logic (so that part doesn't break inadvertently with further changes). 2013-07-04 15:48:55 +00:00
Agetian
abf7382520 - Fixed the logic for the AI planar die roll chance.
- Added an ability to set a chance for the AI hesitating to roll a planar die (default AI at 10%, reckless AI at 0%).
2013-07-04 15:47:32 +00:00
Agetian
6ebfe9b630 - Adding AI hint SVars to Planechase plane cards. Most planes are set to "roll once each turn" for the AI right now, which seems to work fine for a very basic AI (better than nothing). Dangerous or unpredictable planes like Academy of Tolaria West, Isle of Vezuva, Kharasha Foothills, Minamo, Takenuma, Windriddle Palaces are currently set to "never roll" for the AI. Feel free to expand. 2013-07-04 15:40:08 +00:00
Sloth
310577f0c2 - Improved checkSacrificeCost AI saccing lands. 2013-07-04 15:31:27 +00:00
Agetian
2fe6482730 - Modified the default AI profile to account for the new properties as well as for the change of the mulligan threshold property name.
- Added a new AI profile (Reckless) that mulligans more aggressively and rolls planar dice more aggressively.
2013-07-04 15:02:39 +00:00
Sloth
10c285f081 - Added info about chosen player and hauntings to CardDetailPanel. 2013-07-04 15:02:06 +00:00
Agetian
9d1b5833a9 - Minor fixes to the planar dice roll AI.
- Default max rolls per turn and chance to roll are now tied to AI profile properties.
2013-07-04 15:01:51 +00:00
Agetian
eb1234832e - Max AI planar die rolls now correctly check for the number of planar die rolls instead of spell ability activations. 2013-07-04 14:36:02 +00:00
Agetian
3eac899af5 - Minor code cleanup. 2013-07-04 14:24:35 +00:00
Agetian
5195708028 - Fixed the random chance for AI planar die rolls. Removed a debug line. 2013-07-04 14:17:28 +00:00
Agetian
00977986df - Default max number of planar die rolls per turn for the AI is 1. The AI will prefer to roll the planar die in Main 2 unless the AIHintRollDieInMain1:True AI hint SVar is set. 2013-07-04 14:10:28 +00:00
Chris
a5887f7dd6 - Added new card names to changes.txt. 2013-07-04 12:18:06 +00:00
Maxmtg
05cc118282 updated library creation code to generate a single event for the whole library (instead of events on per-card basis) 2013-07-04 09:41:42 +00:00
Maxmtg
cb42cf6ceb added event 'card state changed' to un-animate command 2013-07-04 09:34:40 +00:00
Agetian
8f4dd0225b - Added an override for chkAIDrawback in RollPlanarDiceAi in case it may be necessary in the future. 2013-07-04 08:14:04 +00:00
Maxmtg
e15e7d5ecc Fix possible NPE 2013-07-04 08:11:27 +00:00
Agetian
02d9eaa21b - Renamed a SVar in RollPlanarDiceAi for consistency. 2013-07-04 08:09:50 +00:00
Agetian
982a1c1fd3 - Renamed the SVars for RollPlanarDiceAi, added a possibility to define the maximum number of rolls per turn for the AI. 2013-07-04 08:05:11 +00:00
Sloth
d2067be9d1 - Possible fix for NPE in getCardsAddedThisTurn (wasn't able to reproduce it, so can't check whether it's fixed). 2013-07-04 08:03:13 +00:00
Agetian
2a46a6a9d0 - Added the foundation of the AI for rolling planar dice. By default it does nothing and behaves as the old functionality (never roll the planar die). SVars can be specified signaling if and how often the AI should roll the planar die. Currently limited as I have no idea how to make the AI, for instance, to only use the ability to roll the die once per turn. Feel free to expand this AI as necessary. 2013-07-04 07:30:32 +00:00
Agetian
816c5bb040 - Sound System: fixed the "life loss" sound playing when the player gains life.
- Sound System: added a separate sound (linked to the file "life_gain.wav") for when the player gains life.
2013-07-04 06:37:52 +00:00
jsv
356d9170f0 A small fix for Starfire 3 duel deck (wrong header line resulting in NPE) 2013-07-04 06:31:25 +00:00
swordshine
0d5afe48a8 - M14: Added Bogbrew Witch, Bubbling Cauldron, Festering Newt,, Kalonian Hydra, and Syphon Sliver 2013-07-04 00:27:46 +00:00
Sloth
0eac173451 - Added some SVars to the denizens. 2013-07-03 20:42:03 +00:00
moomarc
355c2e78ec - Added Fractured Powerstone 2013-07-03 19:05:49 +00:00
Agetian
4722471b08 - Specifying which sets always have a foil card in the common slot in boosters. 2013-07-03 19:03:38 +00:00
Agetian
1eed542f46 - Adding a field to specify whether the booster has a foil always in the common slot (FoilAlwaysInCommonSlot=True) or in the slot belonging to the rarity of the card (FoilAlwaywsInCommonSlot=False). 2013-07-03 19:02:44 +00:00
jendave
bb1444b7f9 Bump freemarker dep. Start work on new osx profile. 2013-07-03 18:28:51 +00:00
Maxmtg
4c7ab1a47f rename field in CardDamagedEvent
fix possible "weird NPE" at CMatchUI.java:344
2013-07-03 18:10:02 +00:00
Chris
b67a84800b - Added new card names to changes.txt. 2013-07-03 12:26:23 +00:00
Sloth
7d3ffe324a - Fixed AI's static abilities bypassing addAndUnfreeze (and miss ActivationNumber increase). 2013-07-03 12:17:56 +00:00
Maxmtg
f6ef39867c Implemented partial updates for sincle cards in battlefield 2013-07-03 12:10:34 +00:00
Maxmtg
7a3b82e40b deck lists are properlu updated on click to radiobutton (not hover) 2013-07-03 10:54:43 +00:00
Maxmtg
c3c847c5ff * Zone classes cleanup, they now have reference to game, can contain only cards, used shared code to add cards, generate GameEventZone 2013-07-03 10:45:01 +00:00
Maxmtg
2e4ec0e402 added UI updates in PumpEffect and on Attachment changes 2013-07-03 08:44:01 +00:00
jsv
cca6e94a71 Fixed morphs causing an NPE in AiController.getSpellAbilityToPlay. 2013-07-03 07:07:20 +00:00
Agetian
20addc10ac - Setting the chance of a foil in Modern Masters booster to 100% (does not currently apply as it's not hooked to booster generator yet). 2013-07-03 05:39:33 +00:00
Maxmtg
2a49c009c0 Removed MyObservable, components now recieve updates from game event bus.
Please report UI not updating on time
2013-07-02 23:30:26 +00:00
Sloth
077e4916fc - Fixed type of Ghoulcaller's Chant. 2013-07-02 20:29:56 +00:00
Sloth
ae21ba28c7 - Fixed Legion's Initiative. 2013-07-02 18:39:45 +00:00
Sloth
d5cf59cb16 - Fixed The Very Soil Shall Shake. 2013-07-02 18:24:56 +00:00
Agetian
ba3bb79b52 - Set the default foil chance in booster to 16% (approximately 1/6) which seems to be the average for the majority of sets. Can be overridden in edition definition files via FoilChanceInBooster option. Not currently used by the booster generator. 2013-07-02 17:09:26 +00:00
Sloth
4aed00d954 - Fixed cards moving to the library not creating CombatLKI's. 2013-07-02 15:29:00 +00:00
Chris
1052537fc6 - Added a fluff piece to the changes.txt file. 2013-07-02 12:23:52 +00:00
Maxmtg
6d30afb166 fix npe parsing quest challenges without difficulty 2013-07-02 10:55:54 +00:00
Sloth
60c2ab84d8 - Updated some SVars. 2013-07-01 21:17:54 +00:00
Sloth
4d9b270d3a - Fixed confirmTrigger sometimes revealing hidden information. 2013-07-01 16:43:41 +00:00
Sloth
86bf9f2a28 - Fixed paying mana with Elvish Spirit Guide. 2013-07-01 16:31:55 +00:00
Sloth
ed1e99353f - Expanded NonStackingAttachEffects (R-V). 2013-07-01 16:25:54 +00:00
Sloth
4d7749e265 - Fixed AI attacking with a single Hero of Bladehold twice. 2013-07-01 16:25:13 +00:00
Agetian
368e3c36b9 - fixed the way the foil overlay is drawn over the card picture panel, simplified the related code a little bit. 2013-07-01 14:13:20 +00:00
Agetian
ef44922aa6 - potential NPE prevention. 2013-07-01 12:16:56 +00:00
jsv
84abcc565d Add missing sideboards to M13 intro decks. 2013-07-01 10:07:56 +00:00
Agetian
b3b54ffe00 - Foil cards will now show up as foil in the card picture panel during the match (but not in the deck editor, at least for now). 2013-07-01 10:05:54 +00:00
Agetian
43dae4915f - A more appropriate name for the field representing foil chance in booster packs. 2013-07-01 07:36:33 +00:00
Agetian
990f93a34e - Changed the way foil rate in booster packs is represented in edition definition files (now it's an integer that represents a chance of a foil appearing in a booster pack out of 100%). 2013-07-01 07:24:11 +00:00
Agetian
17b1d4004a - Added preliminary support for metadata that defines if boosters for a particular edition may contain foils. 2013-07-01 03:54:31 +00:00
Chris
41f0012dc1 - Fixed the spell description for Shadow of Doubt, looks like a simple typo. 2013-07-01 02:32:41 +00:00
Sloth
9a3282285c - Fixed possible combat == null NPE in CloneAi. 2013-06-30 21:21:23 +00:00
Sloth
2ad63e7d7f - Fixed Djinn Illuminatus giving Replicate to all spells. 2013-06-30 21:18:17 +00:00
Agetian
abaf65e185 - Adding foil style info to edition definition files. 2013-06-30 17:45:27 +00:00
Agetian
a16b8e3ffc - Updated the loading progress bar to account for the extra foil sprite sheet, part 2. 2013-06-30 17:09:01 +00:00
Agetian
fdebee9011 - Foil cards will now be drawn with black border no matter what edition they are from (this currently has the side effect of drawing early core set foils with a black border around a thin white border already present in the picture - will probably be remedied later in one way or another).
- Updated the loading progress bar to account for the extra foil sprite sheet.
2013-06-30 16:50:43 +00:00
Agetian
00d76439b2 - Card foiling now defaults to modern foil style if the card edition file does not specify the foil type (this re-enables random foiling). 2013-06-30 16:12:23 +00:00
Agetian
b90694142c - Implemented foiling based on card edition-specific information about the availability and style of foil.
- Added the ability to differentiate between old-style and new-style (modern) foils. If a separate foil sheet called sprite_old_foils.png is available, that sheet will be used for pre-8th edition cards. If not available, the same sprite sheet (sprite_foils.png) will be used for all foils.
- Note that the edition files are not yet modified with the information about availability/style of foil cards. This has the nasty temporary side effect of disabling random foiling for all sets (to be remedied in the nearest future).
2013-06-30 16:05:01 +00:00
Sloth
3dac462afd - Expanded NonStackingAttachEffects (H-Q). 2013-06-30 15:45:30 +00:00
Maxmtg
a8ffebce60 remove some duplicated lines 2013-06-30 15:05:49 +00:00
Maxmtg
4a86221e8a Cards activable from other zones now are gathered inside player class, 2013-06-30 15:05:30 +00:00
Chris
7679b05c55 - Added new card names to changes.txt. 2013-06-30 12:55:15 +00:00
Sloth
1efd93e947 - Expanded NonStackingAttachEffects (A-H). 2013-06-30 12:28:55 +00:00
Sloth
9641af3973 - Updates to Attach and Pump AI. 2013-06-30 11:43:59 +00:00
Sloth
953c62f36c - Raised tokenBonus in getBestCreatureToBounceAI. 2013-06-30 10:03:31 +00:00
Sloth
7620b42bef - Fixed NPE in getAttackersOf. 2013-06-30 08:25:21 +00:00
Sloth
f35d57b534 - Fixed NPE in reveal function. 2013-06-30 08:20:10 +00:00
Sloth
10aadd4e66 - Fixed Haunt crash. 2013-06-30 07:59:17 +00:00
Agetian
0af795d2e0 - Added preliminary support for reading foil type information from game edition definition files (doesn't affect actual foiling yet). 2013-06-30 06:13:36 +00:00
Maxmtg
80b403c881 Phase inidicators arranged into a separate control
Moved special and more complex control like DeckChooser aside from the simpliest ones (like FButton or FLabel)
2013-06-30 06:05:48 +00:00
swordshine
5d3db0dbd3 - Added Skyfire Kirin and Puppet Master 2013-06-30 04:46:35 +00:00
Sloth
d015c35912 - Fixed canPlayFromEffectAI withOutManaCost. 2013-06-29 22:15:55 +00:00
Maxmtg
c811662d6f CMessage no longer stores game as model, instead updates follow visitor pattern (which ever game called update, get its information displayed)
Remove unused imports
2013-06-29 22:04:33 +00:00
Maxmtg
8e1b3185fe adjusted splid cards transformation place 2013-06-29 21:49:20 +00:00
Sloth
d892f396fb - Improved hasACardGivingHaste AI function. 2013-06-29 21:22:11 +00:00
Maxmtg
64dfce26b1 inverted the value of variabel for HumanPlaySpellAbility
moved split cards state restoration to the depths of changeZone, removed excessive action from HumanPlaySpellAbility
2013-06-29 21:16:26 +00:00
Maxmtg
d375d5588c TargetRestictions don't need to be copied, they may be just reused 2013-06-29 21:14:42 +00:00
Maxmtg
e1fb9121af hotseat: NPE fix at the beginning of 2nd match 2013-06-29 20:27:17 +00:00
moomarc
7101dc02c4 - Added Scars of the Veteran and Sacred Boon 2013-06-29 18:44:17 +00:00
Maxmtg
8d3067a625 made card recognition insensitive to case of set (will recognize both Cancel|RTR and Cancel|rtr cards in decks and import) 2013-06-29 15:11:20 +00:00
Sloth
5b85dacfa4 - Fixed ChangeZoneAll with origin library. 2013-06-29 13:16:31 +00:00
Sloth
4ea3851fb4 - Fixed Borborygmos challenge. 2013-06-29 11:57:58 +00:00
Chris
6071121323 - Cleared out the changes.txt file, now ready for new material.
- Added new card names to changes.txt.
2013-06-29 11:42:29 +00:00
Sloth
e1fe8c8e28 - AI will now sacrifice creatures for mana before losing control EOT. 2013-06-29 10:12:06 +00:00
swordshine
708b41b8da - Added Sideswipe 2013-06-29 09:25:39 +00:00
Sloth
ec730735bc - Improved ControlGainAi. 2013-06-29 08:24:52 +00:00
swordshine
0e2c6131f6 - Added Heart of Bogardan 2013-06-29 05:34:03 +00:00
Sloth
1172a542fd - Fixed possible NPE in assignAttackersDamage. 2013-06-28 19:47:30 +00:00
moomarc
4b71ea18b8 - Added support for damage prevention shields with effects.
- Added: Candles' Glow, Temper, Test of Faith and Vengeful Archon
2013-06-28 16:56:56 +00:00
Chris
4d8d51facc [maven-release-plugin] prepare for next development iteration 2013-06-28 12:09:55 +00:00
Chris
33cbbbd162 [maven-release-plugin] prepare release forge-1.4.2 2013-06-28 12:09:45 +00:00
Chris
71fb359aec - Preparing the changes.txt file for the next beta build and release. 2013-06-28 11:57:32 +00:00
Chris
879ce2a195 - Added new card names to changes.txt. 2013-06-28 11:43:30 +00:00
jsv
e3d57641ca Added missing sideboards to a couple of event decks for the spell shop. 2013-06-28 11:17:49 +00:00
Maxmtg
6bd754e6d6 fix NPE in AttachAi.attachAIPumpPreference(AttachAi.java:781) 2013-06-28 08:55:56 +00:00
jsv
c419b8d2a6 Fixed possible IndexOutOfBoundsException in chooseCardsToRevealFromHand. 2013-06-28 08:38:16 +00:00
swordshine
77191443ec - Added Quicksilver Sea 2013-06-28 00:25:11 +00:00
Sloth
0c1b3939ea - Added the medium quest opponent Blue Devil 2. 2013-06-27 13:22:08 +00:00
Sloth
088dc4eb99 - Fixed NPE in attachAIPumpPreference caused by Fortifications. 2013-06-27 11:58:05 +00:00
Chris
3019641d9f - Added new card names to changes.txt. 2013-06-27 10:44:10 +00:00
Maxmtg
82bb17ebf9 fixing NPE in CCombat.getCombatDescription. CCombat is given its own copy of combat that cannot be taken away by a different thread. 2013-06-27 08:39:13 +00:00
Sloth
67ee42dcda - Script cosmetics. 2013-06-26 21:47:44 +00:00
Maxmtg
0387d9a82e restore targeting arcs 2013-06-26 21:24:36 +00:00
Sloth
bb22cc6ba9 - Converted the second ability of Fastbond to script. 2013-06-26 21:19:20 +00:00
Sloth
9d78336096 - Time Spiral Timeshifted cards are now modern legal. 2013-06-26 16:14:41 +00:00
Sloth
07590fba08 - Fixed more possible NPE's caused by combat == null. 2013-06-26 14:19:33 +00:00
Sloth
f280ba4e05 - Fixed possible NPE in combatTriggerWillTrigger. 2013-06-26 13:44:11 +00:00
Chris
6e111aab3a - Added a fluff piece to the changes.txt file. 2013-06-26 13:03:08 +00:00
swordshine
5fad51e876 - Added Bloodlord of Vaasgoth (scripted by moomarc) 2013-06-26 12:36:13 +00:00
Chris
df11323434 - Added new card names to changes.txt. 2013-06-26 12:19:15 +00:00
Maxmtg
2c98474e16 Read foiled cards from deck (they mush have a + sign appended right to name, ex: Island+|ZEN) 2013-06-26 07:57:09 +00:00
swordshine
6e2803a614 - Added Herald of Leshrac 2013-06-26 06:24:23 +00:00
swordshine
6ea8469189 - Updated token images 2013-06-26 04:20:32 +00:00
swordshine
0a8064d7b5 - Added Urborg Panther 2013-06-26 01:08:00 +00:00
jendave
ad294fa15a add osx command file as workaround for .app issue 2013-06-25 22:35:32 +00:00
jendave
bebeb43a25 Update dmg builder 2013-06-25 21:27:23 +00:00
Maxmtg
72600d2e84 missing Oracle text for kithkin mourncaller 2013-06-25 20:59:31 +00:00
Maxmtg
193d740133 Removed abstract about not working Abu Ja'far and his friends
Removed abstract about potential Java 7 compatibility. Now it's a must.
2013-06-25 20:56:10 +00:00
Sloth
2e4278a89e - Added an AI SVar. 2013-06-25 20:50:01 +00:00
Maxmtg
2717c0d494 LKI for defenders (part 2 - final). Abu Ja'far now works as intended 2013-06-25 20:26:40 +00:00
Maxmtg
cf3dac5a7d kithkin mourncaller - seems to work now 2013-06-25 19:26:28 +00:00
Maxmtg
56ca218826 CombatLki - part 1. 2013-06-25 19:25:47 +00:00
Sloth
7cbef74401 - Changed the icon name of the The Nac Mac Feegle quest deck to the one downloaded. 2013-06-25 16:15:22 +00:00
Sloth
a228e18669 - Updated the quest deck Oreius 4. 2013-06-25 16:07:28 +00:00
jsv
3f24909423 Fix Master of the Wild Hunt waiting forever in cases when all his wolves have died before damage allocation is complete. 2013-06-25 12:30:42 +00:00
Chris
72c7cb38b6 - Added new card names to changes.txt. 2013-06-25 12:09:41 +00:00
jsv
54983e52c5 Fixed Lazav, Dimir Mastermind loosing his ability. 2013-06-25 11:34:59 +00:00
jsv
6ddf9c6817 Fixed a typo in Spire Tracer's type. 2013-06-25 10:57:22 +00:00
Sloth
3c20f384f5 - Added Joven's Ferrets. 2013-06-25 08:59:39 +00:00
Sloth
f9f4a27c6a - Fixed SacrificeEffect not remembering proper LKI copies. 2013-06-25 08:22:37 +00:00
Sloth
ef8af1a2b6 - Fixed IllegalArgumentException caused by multiple origins in ChangeZoneAll AI. 2013-06-25 08:03:09 +00:00
Sloth
3f4839cfe5 - Fixed Nettling Imp. 2013-06-25 07:41:55 +00:00
Maxmtg
7be40800c1 disabled that groovy compiler for it does not work today. Using regular javac now 2013-06-25 07:05:06 +00:00
Maxmtg
78938459b5 fixed 'blocked' variable of AttackingBand for propper Ninjutsu 2013-06-25 05:56:01 +00:00
Sloth
e2ef4b497c - Fixed description of "CantBeBlockedByAmount GT" keyword. 2013-06-24 20:59:09 +00:00
Sloth
d78b59b9cd - Added "CARDNAME must be blocked if able." to NonStackingKWList.txt 2013-06-24 18:28:01 +00:00
Maxmtg
1908ad7583 added inCombat check to controllerChangeZoneCorrection 2013-06-24 16:10:08 +00:00
Maxmtg
b20da90917 cp.getMatchingForgeCard() hand no owner and belonged to no game - that's why it didn't pass the test invoked by Card.isValid 2013-06-24 16:08:56 +00:00
Maxmtg
69d5be5bd9 getProtectionList static method moved to ProtectEffect.java
ProtectEffect uses game.notifyOfValue to avoid direct calls to Gui
2013-06-24 16:07:57 +00:00
Sloth
8e8ee4bb5c - Fixed prompt of "At the beginning of your upkeep, sacrifice" keywords. 2013-06-24 14:58:04 +00:00
Sloth
7bb5f45c82 - Little script updates. 2013-06-24 14:56:27 +00:00
Maxmtg
b074af6d41 correct the choice out of available flip results 2013-06-24 12:53:22 +00:00
Maxmtg
5038d5e838 Removed calls to GUI and to isHuman in ChooseNumber and FlipCoin effects 2013-06-24 12:48:52 +00:00
Chris
da234f5fcf - Added new card names to changes.txt. 2013-06-24 12:18:01 +00:00
Maxmtg
6ff8ba6682 moved Combat & CombatUtil to package forge.card.combat Sol has created.
removed EndOfCombat and Cleanup classes as they don't add anything specific to base class. Base class Phase is no longer abstract, also it does not need reference to game instance any longer
2013-06-24 10:54:46 +00:00
Maxmtg
dfc630aa23 orderMultipleBlockers and orderBlockingMultipleAttackers - moved into Combat class.
Combat - adjusted visibility of methods, moved some methods closer to caller
2013-06-24 10:40:56 +00:00
Maxmtg
74059bda82 Combat instance lifespan limited to Combat phase (for the rest combat = null, checks will return 'not attacking', 'not blocking'), the very object is stored in PhaseHandler
Card: removed methods to test if card is attacking/blocking, because these properties are related to combat, not the card itself.
AiAttackController - no longer creates Combat. Instead it uses a provided instance and fills attackers there
ComputerUtilBlock.java became non-static class AiBlockController, also modifies the provided Combat instance
2013-06-24 09:33:19 +00:00
swordshine
394a322fbe - Fixed SpellDescription of Time Spiral 2013-06-24 00:27:41 +00:00
Sloth
46786907b6 - Added "Mandatory$ True" to some more card scripts. 2013-06-23 19:09:02 +00:00
Sloth
b11179bc84 - Fixed See Beyond. 2013-06-23 18:32:18 +00:00
Sol
d8115e1cec - Added Nalathni Dragon 2013-06-23 17:49:40 +00:00
Sol
52a26ba4a5 - Added Urza's Engine, Icatian Skirmishers, Camel 2013-06-23 17:20:44 +00:00
Chris
72c7a54879 - Added new card names to changes.txt. 2013-06-23 16:13:57 +00:00
Sloth
9c66737813 - Added Dark Sphere by squee1968. 2013-06-23 13:32:57 +00:00
Sloth
4f159471d7 - Added some AI blocking against creatures with "can't be blocked except by X or more creatures". 2013-06-23 12:15:05 +00:00
Sloth
e63d418b03 - Fixed a NPE in canPlayLand AI. 2013-06-23 12:11:25 +00:00
Sloth
5f3f859b81 - Added Gaea's Touch. 2013-06-23 11:01:35 +00:00
Sloth
90cffa7b37 - M14 rules changes to Indestructible. 2013-06-23 09:55:52 +00:00
Sloth
770a7d20a8 - Fixed DebuffEffect. 2013-06-23 09:35:31 +00:00
Sloth
6b04ca78f9 - Converted Modular to macro script. 2013-06-23 09:18:55 +00:00
Sloth
e5ad11748f - Fixed modular keyword. 2013-06-23 09:02:52 +00:00
Maxmtg
fa38216eaa a minor brush up (use EnumSet instead of switch-case) 2013-06-23 08:04:41 +00:00
Sol
c94e8a395f - Added Bands with others support
- Add 5 Legends Bands with Other lands, Master of the Hunt, Shelkin Brownie, Tolaria
- Small fixes to Combat and CCombat
2013-06-23 01:09:43 +00:00
Chris
dddd7972fb - Added new card names to changes.txt. 2013-06-22 12:11:19 +00:00
Chris
3e4323d10c Updated the org.eclipse.jdt.core.prefs and .classpath files to make them Java 7 compatible. 2013-06-22 12:09:49 +00:00
Sloth
197ca7cf4a - Added the medium quest opponent Hookah-Smoking Caterpillar 2 by Nordos. 2013-06-22 11:52:46 +00:00
swordshine
41827bb653 - some other api with tgtPlayers 2013-06-22 11:09:28 +00:00
swordshine
e7656f3be5 - Additional fix about tgtPlayers 2013-06-22 10:55:21 +00:00
Sloth
17f10cafbc - Fixed not being able to block creatures attacking your Planeswalker. 2013-06-22 10:32:23 +00:00
swordshine
62239488c9 - revert changes in changezoneall effect, complicated situations here 2013-06-22 10:32:12 +00:00
swordshine
615abebe99 - Fixed ChangeZoneAll, TapAll, UntapAll effect 2013-06-22 10:29:43 +00:00
Sloth
b703546c80 - Fixed Anavolver and friends. 2013-06-22 10:19:24 +00:00
swordshine
c862679aac - Fixed PumpAll effect 2013-06-22 10:16:01 +00:00
Sloth
2b2bfa6389 - Fixed Hypergenesis. 2013-06-22 10:08:23 +00:00
Sloth
8e50d42099 - Fixed spells without costs being playable. 2013-06-22 10:08:03 +00:00
Maxmtg
2fd6fde9b5 remove setinfos, part a 2013-06-22 08:49:38 +00:00
Maxmtg
47606cf46c remove setinfos, part z 2013-06-22 08:45:19 +00:00
Maxmtg
2ab15ba0d1 remove setinfos, part y 2013-06-22 08:44:53 +00:00
Maxmtg
0bd68b9b3e remove setinfos, part x 2013-06-22 08:44:39 +00:00
Maxmtg
6cfb6f60f5 remove setinfos, part w 2013-06-22 08:44:32 +00:00
Maxmtg
be1886179f remove setinfos, part v 2013-06-22 08:41:49 +00:00
Maxmtg
65f2368050 remove setinfos, part u 2013-06-22 08:39:48 +00:00
Maxmtg
90a8a2e9da remove setinfos, part t 2013-06-22 08:38:56 +00:00
Maxmtg
9afd057ee2 remove setinfos, part s 2013-06-22 08:34:22 +00:00
Maxmtg
0440c2a89c remove setinfos, part r 2013-06-22 08:23:21 +00:00
Maxmtg
7245506564 remove setinfos, part q 2013-06-22 08:19:29 +00:00
Maxmtg
8c7f10b976 remove setinfos, part p 2013-06-22 08:19:10 +00:00
Maxmtg
cb2fb9e1c1 remove setinfos, part o 2013-06-22 08:15:40 +00:00
Maxmtg
1316cc6c32 remove setinfos, part n 2013-06-22 08:14:18 +00:00
Maxmtg
ddd503fc31 remove setinfos, part m 2013-06-22 08:12:39 +00:00
Maxmtg
ded1fe3db5 remove setinfos, part l 2013-06-22 08:08:05 +00:00
Maxmtg
f6dc60a9f0 remove setinfos, part k 2013-06-22 08:05:55 +00:00
Maxmtg
62e018aced remove setinfos, part j 2013-06-22 08:04:01 +00:00
Maxmtg
799b31f2aa remove setinfos, part i 2013-06-22 08:03:15 +00:00
Maxmtg
89725523d0 remove setinfos, part h 2013-06-22 08:01:36 +00:00
Maxmtg
c14fff73b2 remove setinfos, part g 2013-06-22 07:59:09 +00:00
Maxmtg
81b4156744 remove setinfos, part f 2013-06-22 07:55:07 +00:00
Maxmtg
35a02524c9 remove setinfos, part e 2013-06-22 07:51:57 +00:00
Maxmtg
a0b2bbcb1c remove setinfos, part d 2013-06-22 07:49:40 +00:00
Maxmtg
baf16a2719 remove setinfos, part c 2013-06-22 07:45:27 +00:00
Maxmtg
de15debd11 remove setinfos, part b 2013-06-22 07:40:31 +00:00
swordshine
5253f90284 - Fixed Reveal effect 2013-06-22 05:53:51 +00:00
swordshine
46f4aa8e35 - Reverted last fix, the reveal issue was related to Chancellors 2013-06-22 05:48:18 +00:00
swordshine
8d58e317aa - Fixed Infernal Tutor 2013-06-22 05:37:48 +00:00
swordshine
046836a63a - Fixed Gerrard Capashen 2013-06-22 05:13:16 +00:00
swordshine
92930d7a6f - Added Guile 2013-06-22 03:02:49 +00:00
swordshine
b49d1f3f39 - Added Phyrexian Colossus 2013-06-22 01:12:07 +00:00
swordshine
cba2fb93c9 - Added Goblin Flectomancer and Wild Ricochet 2013-06-22 00:47:59 +00:00
Maxmtg
4c40598c79 refactored 10+ calls to isComputer/isHuman 2013-06-21 22:46:00 +00:00
Maxmtg
69c36da984 added 2 simple creatures with CantBeBlockedByAmount LT3 2013-06-21 21:43:06 +00:00
Maxmtg
a75835f664 CantBeBlockedByAmount + expression used to specify number or creatures that can block given attacker 2013-06-21 21:42:26 +00:00
Sloth
06eb8e3e43 - Added a new AI SVar "NonStackingAttachEffect". 2013-06-21 21:39:55 +00:00
Maxmtg
68d3a7cdb2 more blocking-related keywords replaced with CanBeBlockedBy+expression 2013-06-21 19:13:23 +00:00
Maxmtg
fa443cea0d Text for CantBeBlockedBy is composed in runtime 2013-06-21 18:14:52 +00:00
Sloth
da1645c353 - Fixed Skymark Roc. 2013-06-21 14:51:01 +00:00
Maxmtg
1d498a5ba3 "can be blocked only by creatures with defender" now also uses a common keyword 2013-06-21 14:47:44 +00:00
Maxmtg
f3876079a9 CantBeBlockedBy - attempts to build keyword description 2013-06-21 14:08:30 +00:00
Maxmtg
dc390b796d removed keywords "CARDNAME can't be blocked by {color} creatures." 2013-06-21 14:07:17 +00:00
Maxmtg
09edb071ff removed keyword CARDNAME can't be blocked by red creatures 2013-06-21 13:38:48 +00:00
swordshine
13d28d6ecb - Fixed Timetwister and similar cards. 2013-06-21 12:58:53 +00:00
Maxmtg
b92eaa14a5 Redirect added. M13 is complete. 2 unimplemented cards in T2 remaining. 2013-06-21 12:41:21 +00:00
Chris
10c41c9dfa Updated sound file list. 2013-06-21 12:26:04 +00:00
Chris
6c6e26bcaa Added the converted version of the end_of_turn.wav sound file, thank you jsv 2013-06-21 12:21:20 +00:00
Chris
a75ad8080d - Added new card names to changes.txt. 2013-06-21 12:05:59 +00:00
Maxmtg
1834182d2e add comment, remove unused variable, make simple overload to chooseSingleSpellAbility in AI part of the effect 2013-06-21 11:49:59 +00:00
Maxmtg
a395adc2b6 CopySpellAbilityEffect refactored: fixed Precursor Golem (and hopefully all similiar spells), removed calls to isHuman/isComputer
PlayerController: added chooseSingleSpellForEffect
2013-06-21 11:28:16 +00:00
Sloth
5fe795059b - Fixed targeted triggers not being removed from the stack (again). 2013-06-21 10:52:04 +00:00
Maxmtg
292df0c8de TargetChoice clone = added lost field 2013-06-21 06:49:10 +00:00
Maxmtg
c3e752f98f rearrange and update comments 2013-06-21 06:47:33 +00:00
Maxmtg
56737375da Spellskite script uses a general clause 'ChangeSingleTarget', uses 'Defined' to specify new target
SpellAbility.canTarget now also works for SpellAbilities
2013-06-21 06:31:44 +00:00
swordshine
5fe182f3c4 - Another fix 2013-06-21 06:19:39 +00:00
Maxmtg
3242d396a9 Haunt now resolves, but its trigger won't leave stack 2013-06-21 06:06:55 +00:00
swordshine
390a23d88e - A quick fix the script of Spellskite 2013-06-21 06:06:51 +00:00
Maxmtg
00ddfed039 spellskite added - please test 2013-06-21 04:16:59 +00:00
Maxmtg
775588b300 applied auto fix layout to deck editor 2013-06-21 01:06:25 +00:00
Maxmtg
8a50df0e06 Gideon from GTC - 1st ability will work 2013-06-20 17:46:47 +00:00
Sloth
9c5a38ac70 - Added the hard quest opponent Doctor John Zoidberg 3. 2013-06-20 15:05:12 +00:00
Sloth
ce7a8bb1f1 - Fixed targeted triggers not being removed from the stack. 2013-06-20 14:29:47 +00:00
Chris
77dcb26372 - Added a fluff piece to the changes.txt file. 2013-06-20 11:45:18 +00:00
Chris
0aecaec778 - Added new card names to changes.txt. 2013-06-20 11:41:59 +00:00
Maxmtg
90b48b5447 addition 2013-06-20 06:06:13 +00:00
Maxmtg
89be097c3f fixed missing assignment in PumpEffect 2013-06-20 06:03:34 +00:00
Maxmtg
4fdb9ef7f9 fix endless loop for counterspell 2013-06-20 05:52:26 +00:00
swordshine
68dba80b9c - Added AITgts to Arachnus Web 2013-06-20 02:45:12 +00:00
Sloth
13615dc15a - Fixed possible NPE in getBlockers. 2013-06-19 20:59:42 +00:00
Sloth
8820a45312 - Improved AI's first land drop decision. 2013-06-19 20:39:23 +00:00
Sloth
b5186a367a - Fixed NPE caused by spells without target. 2013-06-19 20:37:37 +00:00
Sloth
24ee2cba60 - Fixed possible NPE's in doPayment functions. 2013-06-19 16:58:38 +00:00
Maxmtg
ade7bed52f SpellAbility holds separate TargetRestricions and TargetChoices in separate members.
Some APIs might become broken, please report
2013-06-19 14:51:21 +00:00
Sloth
3c3ab8138b - Fixed AttackerUnblocked triggers. 2013-06-19 12:42:08 +00:00
Sloth
85c1fc457c - Cleanup of Card class. 2013-06-19 12:34:05 +00:00
Sloth
9525581631 - Improved Stack description of DestroyAll effects. 2013-06-19 12:11:12 +00:00
Sol
9856588fa8 - AiAttackController will use it's own Random object instead of the primary game one 2013-06-19 01:47:02 +00:00
Sloth
eb85f56534 - Fixed AI not attacking planeswalkers. 2013-06-18 20:44:23 +00:00
Sloth
8cce641e43 - Moved isNegativeCounter to ComputerUtil and improved it. 2013-06-18 18:39:03 +00:00
jendave
be237b402c Update windows jar wrapper 2013-06-18 18:35:42 +00:00
jendave
26b9fa20be Update deps 2013-06-18 17:58:49 +00:00
Sloth
9c7efa9037 - Optional triggers will now grant additional info when the human is prompted. 2013-06-18 15:08:20 +00:00
Sloth
2fa0e11ebb - Improved Sigil Blessing stack description. 2013-06-18 14:39:13 +00:00
Sloth
db40ad80ae - Fixed Grave Bramble. 2013-06-18 14:23:17 +00:00
asepetci
11262acafc updated rankings.txt 2013-06-18 13:27:35 +00:00
Maxmtg
b41147b7f6 fixes compile error 2013-06-18 12:56:57 +00:00
Maxmtg
de9c9ff3f3 jsv's fix for problem "I have several gauntlets in progress. No matter which one I select, when I press "Start" it's always the first one in the list that gets loaded." 2013-06-18 11:36:28 +00:00
Maxmtg
7a0d57997d commented out javafx dependency to make sure the rest works 2013-06-18 11:18:44 +00:00
Maxmtg
74aa0a4917 cleanup in Target constructors 2013-06-18 10:04:18 +00:00
Maxmtg
c39bf4ee34 'Target' class no longer uses card in ctor and fields 2013-06-18 09:09:49 +00:00
Maxmtg
25edd60ac5 Using ITargetable instead of Object to return targets of an ability 2013-06-18 08:21:26 +00:00
Sloth
28a94982fe - Copies of spells will now copy the payment that has been made for the original (rule 706.10). 2013-06-18 08:05:50 +00:00
Maxmtg
7c1adf1c2a Added generic types to Swing components that needed them (with transition to Java 7) 2013-06-18 07:59:39 +00:00
Sloth
f6dc001e3a - Fixed type of Infernal Plunge. 2013-06-18 07:41:49 +00:00
Sloth
bff37b60d5 - Fixed canPlayLand not working with "May be played by your opponent". 2013-06-18 06:21:36 +00:00
Maxmtg
f8b5e238f4 use java compiler from JDK 7, use jre 7 in launch4j and pmd 2013-06-18 05:22:00 +00:00
swordshine
56de452158 - Fixed Pulmonic Sliver 2013-06-18 04:29:54 +00:00
swordshine
0d18289699 - Fixed Clash of Realities 2013-06-18 01:04:54 +00:00
Sol
7819893f81 - Improve Combat Panel for banding and blockers that have been removed 2013-06-18 00:10:57 +00:00
Sol
7073eb5494 - Convert Balance to script, Simplify Balancing Act
- Add Restore Balance
- Adding a Balance Effect API
2013-06-18 00:06:51 +00:00
Sloth
9996b26eba - Added Wall of Shards. 2013-06-17 21:51:42 +00:00
Sloth
d2e205a3fc - payManaOptional will now pass on the spellability to payCostDuringAbilityResolve.
- Added support for CostGainLife in payCostDuringAbilityResolve.
2013-06-17 21:48:45 +00:00
Sloth
ae1e2dbbcb - Added Djinn Illuminatus. 2013-06-17 21:05:35 +00:00
Sloth
4105614645 - Added support for Djinn Illuminatus. 2013-06-17 21:02:09 +00:00
Sloth
3753d989b7 - Removed some unnecessary Replicate stuff. 2013-06-17 20:28:36 +00:00
Sloth
fb2d08032d - The AI can now use Delve. 2013-06-17 19:56:26 +00:00
Sloth
1dcd260feb - Fixed Delve description on Death Rattle. 2013-06-17 19:00:33 +00:00
Sloth
fc3ae7bfe9 - Fixed cards dodging destruction during controller changes. 2013-06-17 15:36:47 +00:00
Sloth
dd9715761c - Fixed set of the infestation precon. 2013-06-17 14:03:23 +00:00
Chris
2b35f0d714 - Added new card names to changes.txt. 2013-06-17 10:34:15 +00:00
Maxmtg
18a84c0c6e fixed event dispatch moment - fire only if it won't be replaced. 2013-06-17 07:38:08 +00:00
Maxmtg
39a88cd4d1 fix NPE in public SoundEffectType visit(GameEventCardChangeZone event) 2013-06-17 07:33:50 +00:00
Maxmtg
502a087962 fixed sign for rounded corners 2013-06-17 07:31:48 +00:00
Maxmtg
9de9d4ca53 some cleanup in CardPanel 2013-06-17 06:53:18 +00:00
swordshine
d5a2ef88df - "AddReplacementEffects" in static ability (experimental)
- Added Pulmonic Sliver
2013-06-17 05:33:46 +00:00
swordshine
abcb758a56 - Updated token images
- Added Darksteel Garrison
2013-06-17 00:21:53 +00:00
Maxmtg
2c88d952d2 finer look of cards (no more moire on corners)
white-bordered cards have 1px black outline
2013-06-16 23:53:07 +00:00
Maxmtg
373ce84268 removed specific events that can be generalized by change zone 2013-06-16 23:20:04 +00:00
Maxmtg
9aff5237ea a good general game event for card change zone 2013-06-16 23:12:49 +00:00
Maxmtg
58a333ef99 removed unused classes,
removed literals for basic lands
2013-06-16 22:26:38 +00:00
Maxmtg
5506af61ab Uses more reliable way to detect sets with all basic lands present 2013-06-16 21:41:40 +00:00
Sloth
56df11005b - Prevent NPE in getDefenderByAttacker. 2013-06-16 21:00:56 +00:00
Agetian
f6f80da995 - Sound System: added a new sound event (exile a card), uses the sound file res/sound/exile.wav. 2013-06-16 19:44:51 +00:00
Sloth
8d5f633884 - NPE check with debug output added to TriggerSpellAbilityCast. 2013-06-16 18:09:05 +00:00
Sloth
15b3599a05 - Fixed Replicate. 2013-06-16 18:00:14 +00:00
Sloth
f089ef0c60 - Fixed "X Can't be 0." costs always showing cancel. 2013-06-16 16:49:30 +00:00
Sloth
c513f93329 - Fixed "CARDNAME untaps during each other player's untap step." working on your own untap step. 2013-06-16 14:15:35 +00:00
Sloth
00f55f4044 - Fixed AI ignoring mana restrictions (Myr Superion etc.). 2013-06-16 14:01:47 +00:00
Chris
7741d9f65d - Added new card names to changes.txt. 2013-06-16 12:23:31 +00:00
Sloth
b6418401b3 - Fixed possible NPE in assignAttackersDamage. 2013-06-16 11:24:33 +00:00
swordshine
b7d6f32b89 - Added Urza's Avenger 2013-06-16 08:51:40 +00:00
swordshine
789af07ebe - Added 30 banding cards 2013-06-16 04:23:55 +00:00
Sloth
33981efab1 - Improved AI gaining Threshold. 2013-06-15 21:08:42 +00:00
Sloth
0d05da4e0a - The AI will no longer deck itself with Dig spells. 2013-06-15 14:24:13 +00:00
Chris
97c75209f3 - Added new card names to changes.txt. 2013-06-15 12:38:29 +00:00
Chris
bd7d643f07 - Cleared out the changes.txt file, now ready for new material. 2013-06-15 12:26:32 +00:00
Sloth
ee8049c3bf - Updated some SVars. 2013-06-15 07:43:56 +00:00
Sloth
6821546d51 - Fixed filter in tapPrefTargeting. 2013-06-15 06:51:49 +00:00
swordshine
c188296a82 - Cleanup 2013-06-15 00:44:12 +00:00
swordshine
19d93f9e34 - Added Mindlock Orb and Shadow of Doubt 2013-06-15 00:31:43 +00:00
Sloth
a8b9c92207 - Fixed UnmodifiableCollection.remove call caused by Balance. 2013-06-14 21:55:06 +00:00
Sloth
eaa5a35f47 - Removed unnecessary RuntimeException in getUnlockableEditions. 2013-06-14 21:38:49 +00:00
Sloth
c12c5939c0 - Fixed Ground Seal not working for the AI's ChangeZone abilities. 2013-06-14 21:35:11 +00:00
Sloth
4174317f57 - Added the quest opponent Mystique 2 by Nordos. 2013-06-14 21:21:48 +00:00
Sol
5f508e5d17 - Human may now declare attacks in a band
- Added Benalish Hero
2013-06-14 15:19:21 +00:00
Sol
8eedf4b3f1 - Two small fixes for Damage Assignment related to Banding creatures 2013-06-14 14:21:52 +00:00
Sol
786650f660 - Initial checkin for Combat refactor.
- Introduction of AttackingBands which group Attackers, Blockers and Blocked state.
2013-06-14 12:56:05 +00:00
Sloth
dea4a38a6c - Fixed possible min > max error. 2013-06-14 11:42:30 +00:00
Sloth
7298cbbe0d - Fixed chooseSingleCardForEffect for human player. 2013-06-14 11:26:15 +00:00
Sloth
a0f4ac195d - Fixed optional triggers declined by the AI not being removed from the stack. 2013-06-14 11:18:11 +00:00
Chris
7ee57a381a [maven-release-plugin] prepare for next development iteration 2013-06-14 11:07:00 +00:00
18516 changed files with 195658 additions and 173671 deletions

View File

@@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
<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.6"/>
<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.testng.TESTNG_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>

30931
.gitattributes vendored

File diff suppressed because it is too large Load Diff

30
.gitignore vendored
View File

@@ -2,20 +2,32 @@
/*.iml
/*.tmp
/.metadata
forge-ai/forge-ai.iml
forge-ai/target
forge-core/forge-core.iml
forge-core/target
forge-game/target
forge-gui/forge-gui.iml
forge-gui/forge.profile.properties
forge-gui/res/*.log
forge-gui/res/PerSetTrackingResults
forge-gui/res/cardsfolder/*.bat
forge-gui/res/decks
forge-gui/res/layouts
forge-gui/res/pics*
forge-gui/res/pics_product
forge-gui/target
forge-gui/tools/PerSetTrackingResults
forge-gui/tools/oracleScript.log
forge-m-android/bin
forge-m-base/bin
forge-m-desktop/bin
forge-net/target
/forge.profile.properties
/nbactions.xml
/pom.xml.next
/pom.xml.releaseBackup
/pom.xml.tag
/release.properties
res/*.log
res/PerSetTrackingResults
res/cardsfolder/*.bat
res/decks
res/layouts
res/pics*
res/pics_product
/target
/test-output
tools/PerSetTrackingResults
tools/oracleScript.log

View File

@@ -4,36 +4,7 @@
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
</natures>
</projectDescription>

View File

@@ -1,16 +1,15 @@
#Wed Jul 27 18:40:11 EDT 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.compiler.source=1.7
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_annotation=0

View File

@@ -1,219 +0,0 @@
Forge Beta: 06-14-2013 ver 1.4.1
12819 cards in total.
-------------
Release Notes
-------------
- Java 7 -
The devs are discussing a plan to update the battlefield display code with Java FX 2.2 and this should help to improve a few things. Java FX 2.2 requires Java 7 so please update your Java runtime environment in the nearest future. At some point a new version of Forge will no longer run under Java 6.
- New M14 cards -
We have added a branch to our SVN for the new cards that are currently being scripted. These cards are not yet available in this build of forge. Please be patient and they will soon become available.
- Match and Deck Editor Layout problems -
The match and deck editor windows contain panels that can be moved and/or resized. The changes that you make are saved to files that are named "editor.xml" and "match.xml". These files can be found in your userDir/preferences/ directory.
Sometimes people will decide that they do not like the changes that they made and wish to go back to the original layout. To reset layouts to deafult, go to the Game Settings -> Preferences -> Troubleshooting section. You will find at this location two buttons that will reset the match layout and the deck editor layouts.
Also use the mentioned measure if your match or deckeditor won't start - it would help in 90% of the cases.
- The AI Drafting has been improved -
The AI evaluated the basic lands higher than anything else. Fixed. The AI would pick cards with RemAIDeck but only at a much lowered pick rate. For example the best pick in a 250 card set would become the 75th best pick, the 20th best pick would become the 95th and so on. Divided this factor by 3 (so the first pick would become the 25th pick). Please test whether this has improved the draft experience.
---------
New Cards
---------
Amulet of Quoz
Aphetto Dredging
Archive Trap
Battlefield Scrounger
Chain Stasis
Chancellor of the Annex
Chisei, Heart of Oceans
Choking Vines
Cobra Trap
Defensive Formation
Dream Chisel
Dream Leash
Exiled Doomsayer
Fossil Find
Gemstone Caverns
Grave Consequences
Grimoire Thief
Hankyu
Hibernation's End
Indentured Djinn
Ion Storm
Jester's Scepter
Jetting Glasskite
Jotun Grunt
Kira, Great Glass Spinner
Kithkin Armor
Krark's Thumb
Leashling
Liquid Fire
Martyr of Bones
Master Warcraft
Melee
Minion of Leshrac
Odric, Master Tactician
Patron of the Akki
Patron of the Kitsune
Patron of the Moon
Patron of the Nezumi
Patron of the Orochi
Penance
Power Conduit
Prowling Pangolin
Psychic Vortex
Research // Development
Search for Survivors
Shimmering Glasskite
Spinning Darkness
Summoning Trap
Tainted Specter
Teferi's Curse
Temporary Truce
Thelon's Chant
Thought Lash
Thran Turbine
Tidal Influence
Time and Tide
Tourach's Chant
Truce
Uba Mask
Void Maw
----------
New Planes
----------
Furnace Layer
Kharasha Foothills
Mimano
Mirrored Depths
--------------------
New M14 branch Cards
--------------------
Accursed Spirit
Advocate of the Beast
Ajani's Chosen
Archangel of Thune
Awaken the Ancient
Battle Sliver
Blur Sliver
Bonescythe Sliver
Charging Grffin
Corpse Hauler
Dawnstrike Paladin
Deathgaze Cockatrice
Devout Invocation
Elvish Mystic
Enlarge
Fleshpulper Giant
Glimpse the Future
Grim Return
Groundshaker Sliver
Guardian of the Ages
Hive Stirrings
Hunt the Weak
Into the Wilds
Jace's Mindseeker
Kalonian Tusker
Liliana's Reaver
Marauding Maulhorn
Master of Diversion
Megantic Sliver
Molten Birth
Ogre Battledriver
Predatory Sliver
Primeval Bounty
Regathan Firecat
Ring of Three Wishes
Rise of the Dark Realms
Scourge of Valkas
Sentinel Sliver
Seraph of the Sword
Shadowborn Apostle
Shadowborn Demon
Sliver Construct
Soulmender
Sporemound
Staff of the Death Magus
Staff of the Flame Magus
Staff of the Mind Magus
Staff of the Wild Magus
Steelform Sliver
Stonehorn Chanter
Striking Sliver
Thorncaster Sliver
Undead Minotaur
Vampire Warlord
Vastwood Hydra
Vial of Poison
Windreader Sphinx
Woodborn Behemoth
Young Pyromancer
------------
Known Issues
------------
A small number of cards including Wall of Corpses, Abu Ja'far and others with a similar ability are not functional. The common issue here is actually that they check the blocker/attacker after being removed from the battlefield. Probably need to use LKI. Regular destroy/destroy all effects work just fine. The problem cards are caring about battlefield specific properties, that no longer apply when the base card is no longer on the battlefield.
Several people have noticed that the cards displayed on the battlefield will fail to be displayed when the number of cards on the battlefield increases. Maximizing the human panel can help to re-display the cards.
Some time was spent turning the static ETB triggers into the proper ETB replacement effects they should be, mainly to interact correctly with each other. This work is not yet finished. As a result there is currently some inconsistencies with "Enters the battlefield with counters" (Not incredibly noticeable).
A recent contribution to the code base should fix some of the bugs that people noticed with cloning type abilities. At this time there is one remaining issue that we hope will be addressed in the near future:
Copies of cards that setup Zone Change triggers via addComesIntoPlayCommand and addLeavesPlayCommand will not function correctly.
Forge is likely to be compatible with Java 7 at this time. Some people have used forge with Java 7 and have not reported any problems that are related to Java 7. If you would like to upgrade to Java 7 and have held off because of Forge then you may upgrade as we do not think that it will cause an incompatibility type of problem. We will continue to try to maintain compatibility with Java 6 for the foreseeable future.
The Forge archive includes a readme.txt file and we ask that you spend a few minutes reading this file as it contains some information that may prove useful. We do tend to update this file at times and you should quickly read this file and look for new information for each and every new release. Thank you.
The archive format used for the Forge distribution is ".tar.bz2". There are utilities for Windows, Mac OS and the various *nix's that can be used to extract/decompress these ".tar.bz2" archives. We recommend that you extract/decompress the Forge archive into a new and unused folder.
Some people use the Windows application 7zip. This utility can be found at http://www.7-zip.org/download.html. Mac users can double click on the archive and the application Archive Utility will launch and extract the archive. Mac users do not need to download a separate utility.
----------------------------
Contributors to This Release
----------------------------
Agetian
Asepetci
Diogenes
Gos
Hellfish
Marc
Max
Nordos
RedDeckWins
Sidereal
Sloth
Sol
Swordshine
Chris H
(Quest icons used created by Teekatas, from his Legendora set http://raindropmemory.deviantart.com)
(Thanks to the MAGE team for permission to use their targeting arrows.)
(Thanks to http://www.freesound.org/browse/ for providing some sound files.)
end

View File

@@ -1,700 +0,0 @@
Installation Instructions:
--------------------------
The archive format used for the Forge distribution is ".tar.bz2". There are utilities for Windows, Mac OS and the various *nix's that can be used to extract/decompress these ".tar.bz2" archives. We recommend that you extract/decompress the Forge archive into a new and unused folder.
Some people use the Windows application 7zip. This utility can be found at http://www.7-zip.org/download.html. Mac users can double click on the archive and the application Archive Utility will launch and extract the archive. Mac users do not need to download a separate utility.
Once the Forge archive has been decompressed you should then be able to launch Forge by using the included launcher. Launching Forge by double clicking on the forge jar file in the past caused a java heap space error. Forge's memory requirements have increased over time and the launchers increase the java heap space available to Forge. Currently you can launch Forge by double clicking on the forge jar file without a java heap space error but this is likely to change as we add in more sounds, icons, etc.
Updating to a newer version Instructions:
-----------------------------------------
- User data migration -
User data files, like decks, saved gauntlets, and card pictures, are now stored in new directories separate from the program data. When this version of Forge is first run, it will scan the program directory for all user data and automatically migrate the files to their new homes. There are three defined user data directores: userDir, cacheDir, and cardPicsDir, and their locations depend on the standard paths for your operating system:
Windows:
userDir=%APPDATA%/Forge/
cacheDir=%LOCALAPPDATA%/Forge/Cache/ (or %APPDATA%/Forge/Cache/ for windows versions before the local/roaming directory split)
OSX:
userDir=$HOME/Library/Application Support/Forge/
cacheDir=$HOME/Library/Caches/Forge/
Linux:
userDir=$HOME/.forge/
cacheDir=$HOME/.cache/forge/
The appdata directory is hidden by default in Win7. Open a Windows Explorer window (or double-click on My Computer] and in the address field type "%appdata%/forge/" (without the quotes). If that doesn't work, type "%appdata"/roaming/forge".
cardPicsDir is defined as <cacheDir>/pics/cards/ by default. If you wish to use a non-default directory, please see the forge.profile.preferences.example file located in the Forge installation directory root. You can use this file to, for example, share the card pics directory with another program, such as Magic Workstation.
If you are using the Mac OS X version of Forge then you will find the forge.profile.preferences.example file by right clicking or control clicking on the Forge.app icon. Select "Show Package Contents" from the contextual menu. A Finder window will open and will display a folder named Contents. Navigate to the folder:
/Contents/Resources/Java/
and you will find the file named forge.profile.preferences.example.
For reference, here is the full list of moved directories and files:
Old location New location
---------------- ----------------------
res/decks/ <userDir>/decks/
res/gauntlet/ <userDir>/gauntlet/
res/layouts/ <userDir>/preferences/
res/preferences/ <userDir>/preferences/
res/quest/data/ <userDir>/quest/saves/
res/pics/ <cacheDir>/pics/
forge.log <userDir>/forge.log
- New Import Data dialog -
The Import Pictures dialog, accessed via the Content Downloaders submenu, has received an overhaul and has been reincarnated as the Import Data dialog. You may recognize it from the automatic data migration procedure if you had any data to migrate when this version was first started. Instead of just importing pictures from a previous version of Forge, it can now import any kind of Forge data whatsoever. If you have a directory full of deck files, you can use the Import Data dialog to copy or move them to the appropriate directory. If you have just downloaded a torrent of high-quality pics for a particular set, use the Import Data dialog to get them to the proper place. The dialog give you a full listing of all file copy/move operations, so you can see what will happen before you click 'Start Import'.
An importer option was added for including pictures in set-specific card directories that don't map to any currently known card. This handles the case where people have collected complete sets of pics in anticipation of when Forge supports them.
The Mac OS application version:
-------------------------------
We have packaged the Forge BETA version as a Mac OS application. You can double click the Forge.app icon to launch the forge application on your Apple computer running Mac OS. This application will automatically increase the java heap space memory for you as it launches. This version does not require the forge.command file and it does not need to start the Terminal application as part of the start up process.
If you update your OS to Apple OSX 10.8 Mountain Lion and try to launch a new version of forge that you will likely get a dialog which states "File is damaged and cannot be opened. Please move to trash."
Mountain Lion comes with a new Gatekeeper feature and this is probably blocking your ability to launch this newer version of forge. Visit the link below and follow the instructions. They are fairly long and detailed.
http://support.apple.com/kb/HT5290?viewlocale=en_US&locale=en_US
Please note that the issue is most likely caused by Mountain Lion's Gatekeeper feature and it is extremely unlikely that the forge dev team will attempt to get a unique Developer ID from Apple and use it to digitally sign our forge app.
Picture location info:
----------------------
The instructions that were found in this section are now out of date. Current instructions can be found in the CHANGES.txt and forge.profile.properties.example files.
Launching Forge and Memory Issues:
----------------------------------
In the past, some people noticed java heap space errors and lengthy pauses. The memory requirements for Forge have increased over time. The default setting on your computer for the java heap space may not be enough to prevent the above problems.
The technically proficient can launch the forge jar with an argument from the CLI. The argument "-Xmx512m" may work for computers with 1 Gig of memory. Computers with 2 Gigs or more of memory should be able to use "-Xmx1024m" as an argument.
We have created several scripts that will launch the Forge jar with "-Xmx1024m" as an argument. People using Windows OS should double click the "forge.exe" file. People using one of the other *nix OS should double click the "forge.sh" file. People using Apple's Mac OS X should download the Mac version of Forge and then double click the "forge.app" application.
The script file must be located in the same folder as the Forge jar file and the Forge jar file name can not be changed. Otherwise, the scripts will not work.
If you have a low end machine you may find that the scripts above will prevent java heap space errors but will find that Forge still runs very slowly at times.
In this case you can try the following. You can try using low quality pictures rather than the high quality pictures. Or you can try removing all of the jpg pictures from the pics folder.
Forge failed to launch:
-----------------------
If you're trying to run Forge for the first time, but it doesn't open up, you can try the following to get some output and help yourself/us solve the problem.
1) Open up a terminal
- Under Windows, press Windows+R, type "cmd", hit enter
- Under Linux, you probably know that yourself. Use your distribution's application menu, and search for "terminal" in a group like "utilities".
- Launch the program named "Console.app" which can be found in your /Applications/Utilities/ folder. Highlight the "All Messages" option and click on the "Clear Display" button before launching Forge.
2) Go to the folder where you unpacked Forge
- Windows: Let's say your forge is in D:\Programs\Forge.
- Type "D:", Enter to change to the D: drive.
- Type "cd \Programs\Forge", Enter to change to the directory.
- NOTE: On nonenglish systems, you might have problems due to the poor localization of Windows. Go to the innermost directory you find (worst case is "\"), then "dir", Enter to show all folders in that folder. Search for the one you're probably wanting. For Example the German "Programme" could really be "Program Files" or something like that.
- NOTE: You might have to "quote" directory names with Spaces in them
- Linux: Let's say your forge is in /home/user/Forge
- Type "cd /home/user/Forge", Enter
- NOTE: You might have to "quote" or 'quote' directory names with Spaces in them
- Current versions of Forge no longer include a launcher script for Mac OS, proceed to step three.
3) Run Forge
- On Windows, just type "forge.exe", Enter
- On Linux, just type "forge.sh", Enter
- Launch the Forge application bundle by double clicking on the program named "Forge.app".
Now you will probably see some sort of Error in the console. the first few lines contain a message that might help you. If you can't fix the problem yourself, please take the complete output and report your problem on the Forum.
The Card Pictures disappear when you restart Forge:
---------------------------------------------------
if you're running Windows 7, make sure you're running the program as an admin, otherwise no changes will be made to your system (nothing is saved). In Windows 7, Forge may be happier when run from somewhere in the My Documents structure, (they call them Libraries now???) or from another partition other than C:. The user has little permission to do much on the system drive.
Java Issues:
------------
Forge is likely to be compatible with Java 7 at this time. Some people have used forge with Java 7 and have not reported any problems that are related to Java 7. If you would like to upgrade to Java 7 and have held off because of Forge then you may upgrade as we do not think that it will cause an incompatibility type of problem. We will continue to try to maintain compatibility with Java 6 for the foreseeable future.
Forge requires Java 6 and will not run if you have an earlier version of Java. You will need to update to Java 6.
Card Picture Issues:
--------------------
The server which contained the high quality card pictures is now off line and these high quality card pictures are no longer available as a download from within the forge application. We apologize, but the current dev team do not maintain this server and this matter is out of our control.
Some people are choosing to re-download all of the low quality card and card set pictures when they install the next version of forge. This consumes large amounts of bandwidth needlessly. Please be careful!
The instructions that were found in this section are now out of date. Current instructions can be found in the CHANGES.txt and forge.profile.properties.example files.
When you install a new version of Forge, please follow the instructions that can be found in the CHANGES.txt and forge.profile.properties.example files. This will allow you to reuse your previous picture files and other user data files. This way you will only have to download the pictures for the new cards.
This should save enough bandwidth that everyone will be able to download the new set pictures from the cardforge server. We do appreciate your efforts to save bandwidth. Thank you.
Reporting Bugs:
---------------
To report a bug with an official beta release, please follow the instructions at http://www.slightlymagic.net/wiki/Forge#I_think_I_found_a_bug_in_Forge._What_do_I_do.3F .
To report a bug (1) with an alpha test, (2) with a nightly build, (3) with something compiled from the official Forge software repository, or (4) with the leading edge (formerly "SVN Bug Reports"), please do not submit your bugs to the forum. Instead, please follow the instructions at http://www.slightlymagic.net/wiki/How_to_File_a_Bug_Report_with_Mantis.
Forge will now allow you to upload a crash report to the Forge forum at CCGH.
A new very hard tier category in Quest mode:
--------------------------------------------
You will notice a new very hard tier category for the opponent. As you change from the previous tier to the next tier (easy to medium, etc.) you will now notice that there is not an abrupt change over. There is now a mixture of decks from the previous tier and the next tier for you to chose from. When you win a match you will complete the advancement to the next tier.
The Forge Booster Draft mode:
-----------------------------
A significant re-write of the Booster Draft functionality has taken place. Draft from the Full card pool, sets/blocks or custom drafts (like cube). The AI will pick cards more intelligently, and builds decks from picked cards. Old method would pick cards for deck and then stop picking new cards.
The developer mode:
-------------------
The developer mode gives us a few new features. These new features will primarily interest the devs as it will now help to test out specific cards while testing combat code changes. You can turn on or off this mode at the Home View -> Game Settings -> Preferences -> Gameplay Options section.
When turned on the battlefield will have a tab named "Dev Mode". There are a number of useful options in this tab. You can disable loss by milling, generate mana in your mana pool, tutor for card, etc.
New foil card image available:
------------------------------
Rob and Marc have worked together to add in a new feature which will overlay a card's image with a foil like film. A few random cards will have a foil like image in constructed mode games and possibly quest mode games. There is a check box on the Home View -> Game Settings -> Preferences view that you can use to turn this feature on or off.
Informational icons overlays for cards on the battlefield:
----------------------------------------------------------
The Battlefield will now display three symbolic icons that will overlay creature cards. These icons are used to denote summoning sickness and whether a creature is attacking or blocking. Added additional icons that will be drawn over the cards in the battlefield for phasing and counters at a later date. The attack/block/phasing icons and counters will now also be shown on large cards, only casting cost will be omitted. Lands and tokens with different amounts/types of counters will no longer stack. Tokens with different summoning sickness will no longer stack. Lands that become creatures will now always be moved to the front row.
Optional choice for abilities that are on the stack:
----------------------------------------------------
When a spell or an ability appears on the stack and it says "(OPTIONAL)" you can right-click it to decide if you want to always accept or to decline it.
Multiple quest files:
---------------------
Multiple quest files are now supported. This allows you to start a new quest and give it a unique name, and it will not overwrite your previous quest game file.
The new UI now uses tabbed panes:
---------------------------------
We now have a tab system for sub-menus in the home screen. Quest mode refactored to fit this tab system. It's now considerably easier to use - less cramped, less clicks, more functionality.
The quest mode card shop:
-------------------------
You can now buy PreCon decks, Starter packs, Tournament packs and Fat packs from the quest mode card shop.
Player Avatar pictures:
-----------------------
The UI has a few new features including the option to pick an avatar from a collection of pictures. This can be accessed from the Settings -> Avatars tab.
The organizational structure of the /res/decks/ folder:
-------------------------------------------------------
The organizational structure of the /res/decks/ folder has been improved and we now have these six subdirectories:
/decks/constructed/
/decks/cube/
/decks/draft/
/decks/plane/
/decks/scheme/
/decks/sealed/
You can place your deck files from an earlier version of Forge into the /res/decks/ folder. When you next launch Forge these decks will be converted to a newer format and will be moved into the proper subdirectory.
Please not that your /decks/ directory no longer resides in you /res/ directory and has been moved to <userDir>/decks/.
User-created themes for Forge's background, fonts, colors and icons:
--------------------------------------------------------------------
When you select a new skin in the Preferences view Forge should save the change to the preference file, quit and then automatically re-launch with the new skin displayed. During testing some people have noticed that Forge is not restarting on their computer and they have to re-launch Forge themselves.
If anyone is interested in creating additional themes for inclusion in the Forge project then you should visit this topic at CCGH:
http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8449
The Battlefield UI:
-------------------
The Battlefield UI has a new feature implemented which allows us to move and resize the panels to new places on the battlefield. This allows us to personalize the battlefield display to our own liking. You should try moving panels by clicking and dragging their tabs.
If you do not like your efforts to personalize the battlefield display you can revert the display to the default layout by clicking on the Dock button labeled "Revert layout".
The pets in quest mode:
-----------------------
Some adjustments to the pets in quest mode were made. The quest mode plant wall's Deathtouch ability was deemed to be too strong against the AI's attack code and this ability was changed to Wither in this version. This includes a new pet.
The dock tab has a new button labeled "Open Layout":
----------------------------------------------------
The dock now has a new button labeled "Open Layout" along with old button with original function "Revert Layout". Modifying the battlefield layout will result in your changes being saved to a file named "match_preferred.xml". You can copy and rename that file to share your layouts with other people.
The new Deck Editors:
---------------------
The work on the new UI is now finished and this version adds the new UI to the deck editors. We all would like to thank Doublestrike for his contributions to the new UI.
The new deck editors include:
* a better text search (can search for multiple terms, and "not" terms
* interval filters for P/T and CMC
* add/remove 4
* better statistics and draw probabilities
* Toggle-able, sort-able, resize-able, move-able columns
* and of course uses the drag cell layout.
Performance issues on low end machines:
---------------------------------------
Several people have noticed forge slowing down after playing a number of matches without quitting forge in between the matches that are played. The new UI may be involved somehow. We also hope to have this figured out and fixed in the near future. Please be patient in the meanwhile. A recent fix was implemented that should improve the slowdown problem somewhat.
A lot of time and effort have gone into fixing the memory leak problems that were recently noticed and reported to the dev team. Doublestrike and Slapshot deserve our applause and we are very thankful. People should be able to now play long multi match sessions without noticing slow downs and pauses.
Some performance changes were made to Forge and it should now operate more quickly on low end machines. Mid to high level machines are unlikely to notice as much of a performance increase. We tried to hunt down all of the bugs that resulted from these changes but there may still be a bug or two in this beta release.
A note about winning booster packs in quest mode:
-------------------------------------------------
If you win a quest mode match, you get a booster pack for every 1 or 2 (default) Wins, depending on the difficulty level. If you lose and you are playing on easy mode, you get a booster pack every 1 (default) Loss.
The new UI:
-----------
The first step was to update the battlefield window. The second step was to update the New Game window (now named Home view). We got constructed mode and then quest modes working first. We got the draft and sealed modes working again afterwards.
The work on the new UI is now for the most part finished. We should not expect major changes or major additions to the UI. Future betas may include a few minor bug fixes to the UI. And we may also include a few minor tweaks.
The new Alpha Strike button:
----------------------------
A new Alpha Strike button was added to the dock. The Dock is one of the tabs availble in the battlefield view.
Using Forge with the new Mac OS Mountain Lion:
----------------------------------------------
If you update your OS to Apple OSX 10.8 Mountain Lion and try to launch a new version of forge that you will likely get a dialog which states "File is damaged and cannot be opened. Please move to trash."
Mountain Lion comes with a new Gatekeeper feature and this is probably blocking your ability to launch this newer version of forge. Visit the link below and follow the instructions. They are fairly long and detailed.
http://support.apple.com/kb/HT5290?viewlocale=en_US&locale=en_US
Please note that the issue is most likely caused by Mountain Lion's Gatekeeper feature and it is extremely unlikely that the forge dev team will attempt to get a unique Developer ID from Apple and use it to digitally sign our forge app.
The Forge sealed deck mode:
---------------------------
Sealed Deck mode has had a complete rewrite. Full cardpool, block and custom modes are supported. Custom sealed files in the res/sealed folder are exactly the same as custom draft files, except the file extension ".sealed".
A distinction may now be made between AI decks and Human decks, with the addition of a deck Metadata "PlayerType", which right now just helps by sorting human decks into the human combo box and AI decks into the AI combo box.
The Forge sealed deck mode has undergone significant changes. You can find these in the 1.2.14 beta and later versions. Instead of a single sealed deck match, you can now choose a 1-5 round gauntlet-style tournament where you will face increasingly difficult (probably) opponent decks. You can also choose to use starter packs instead of boosters in the block mode, choose to use 3-12 boosters instead of the default 6 in the full cardpool and custom (cube) modes, and so on.
Perhaps the most notable changes to the sealed deck mode are related to "fantasy blocks" and the greatly increased flexibility you have when you are building your own blocks.
The new Gauntlet mode:
----------------------
A new Gauntlet mode has been added. This mode gives you four options: Quick Gauntlet, Build A Gauntlet, Load Gauntlet and Gauntlet Contests. You can create a group of computer decks to play against by choosing either Custom user decks, Quest Decks, Fully random color decks or Semi-random theme decks.
The new Variant mode (was named Multiplayer):
---------------------------------------------
A new multiplayer mode has also been added. You should be able to play against multiple AI opponents at this time. You should note that the current Archenemy mode does not use Schemes at this time.
A lot of things are planned for this new multiplayer mode and it will take time to finish. Please enjoy what we have at this time and be patient. :)
Since Multiplayer is so new, not all cards will be 100% compatible right away as we expand scripting to handle multiple players.
The older match layout files are incompatible with the new multiplayer mode. The original match_default.xml, match_preferred.xml and the match_preferred.xml saved to a different name files have to go and can no longer be used. You can keep your editor_preferred.xml file. But you will have to setup your match view panels using the new match_default.xml file.
The new damage dialog:
----------------------
The new damage dialog now uses the new UI.
When choosing cards, sources, etc. using a list box:
----------------------------------------------------
When choosing cards, sources, etc. using a list box, the currently selected card will now be visually highlighted on the play field (to better distinguish between e.g. three different cards with the same name on the play field). Now the visual highlighting of a card will also work when declaring the order of blockers.
Return to Ravnica Guild Sealed Deck mode:
-----------------------------------------
Added Return to Ravnica Guild Sealed Deck mode. Start a new sealed deck game, choose "Block / Set" and then scroll down until you find "Return to Ravnica Guild Sealed (block)". Select that. From the "Choose Set Combination" menu, select the first option. You will be prompted twice to pick your guild (once for the promo cards, once for the actual booster - you should choose the same guild both times). After that you're ready to go.
Targeting arrows are now available in the battlefield display:
--------------------------------------------------------------
The Targeting Overlay has been fixed and re-enabled. It now correctly shows the targeting arcs in cases when it previously showed them in the wrong direction. The match UI is properly refreshed when the targeting arcs are switched on/off. The defunct "mouseover-only" mode is currently disabled (it crashes Forge, difficult to fix).
The visuals for targeting arrows has been improved and looks better, with an adaptation of the arrow drawing code from MAGE. Thanks to the MAGE team for permission for the adaptation.
Some people have noticed slowdowns when Targeting arrows are enabled. The battlefield Dock tab includes a targeting icon. You can set the targeting arrows to Off or to Card mouseover to speed up the game.
The new sound system:
---------------------
Forge now has a sound effect system in place. Several basic sounds are linked to the code now and will be enabled when "Enable Sounds" option is checked in the preferences. It supports WAV and AU file formats.
Currently supported sound effects are:
(the ones already available in the standard installation of Forge are marked with a [*])
AddCounter [*] - add_counter.wav - triggered when a counter is added to a permanent.
Artifact[*] - artifact.wav - triggered when an artifact is played.
ArtifactCreature [*] - artifact_creature.wav - triggered when an artifact creature is played.
BlackLand [*] - black_land.wav - triggered when a land with the "B" mana ability is played.
Block [*] - block.wav - triggered when a blocker is assigned.
BlueLand [*] - blue_land.wav - triggered when a land with the "U" mana ability is played.
Creature [*] - creature.wav - triggered when a creature is played.
Damage - damage.wav - triggered when a creature is damaged.
Destroy [*] - destroy.wav - triggered when a permanent is destroyed.
Discard [*] - discard.wav - triggered when a player discards a card.
Draw [*] - draw.wav - triggered when a player draws a card.
Enchantment [*] - enchant.wav - triggered when an enchantment is played.
EndOfTurn [*] - end_of_turn.wav - triggered at the end of turn.
Equip [*] - equip.wav - triggered when an equipment is equipped.
FlipCoin [*] - flip_coin.wav - triggered when a coin is flipped.
GreenLand [*] - green_land.wav - triggered when a land with the "G" mana ability is played.
Instant [*] - instant.wav - triggered when an instant is played.
LifeLoss [*] - life_loss.wav - triggered when a player loses life.
LoseDuel[*] - lose_duel.wav - triggered when a player loses a duel.
ManaBurn - mana_burn.wav - triggered during a mana burn if the appropriate rule is enabled.
OtherLand - other_land.wav - triggered when a land with non-color mana abilities or any other land is played.
Planeswalker [*] - planeswalker.wav - triggered when a planeswalker is played.
Poison [*] - poison.wav - triggered when a player receives a poison counter.
RedLand [*] - red_land.wav - triggered when a land with the "R" mana ability is played.
Regen - regeneration.wav - triggered when a creature is regenerated.
RemoveCounter - remove_counter.wav - triggered when a counter is removed from a permanent.
Sacrifice - sacrifice.wav - triggered when a permanent is sacrificed.
Sorcery [*] - sorcery.wav - triggered when a sorcery is played.
Shuffle [*] - shuffle.wav - triggered when a player shuffles his deck.
Tap [*] - tap.wav - triggered when a permanent is tapped.
Token [*] - token.wav - triggered when a token is created.
Untap [*] - untap.wav - triggered when a permanent is untapped.
WhiteLand [*] - white_land.wav - triggered when a land with the "W" mana ability is played.
WinDuel [*] - win_duel.wav - triggered when a player wins the duel.
All sounds use the event bus model now and are not called directly.
The new Vanguard mode:
----------------------
We now have a Vanguard mode implemented. This is a work in progress. The older match layout files are incompatible with the new Vanguard mode. The original match_default.xml, match_preferred.xml and the match_preferred.xml saved to a different name files need to be deleted and can no longer be used. You can keep your editor_preferred.xml file. But you will have to setup your match view panels using the new match_default.xml file.
The new Archenemy mode:
-----------------------
Schemes have been added to the Archenemy mode. This is a work in progress and there may be a bug or two for us to find.
Quest Worlds:
-------------
This version allows you to travel between the regular quest world and the other worlds (Shandalar, Ravnica, Jamuraa, more may be added in the future) to get different duel opponents and challenges. You will have to complete your current challenges before traveling or you will lose them.
World-specific format enforcing and starting world selection are available. Something has to be done about locked (non-repeatable) challenges so they do not end up locking other challenges in different worlds.
Forge now has sideboards for the human player:
----------------------------------------------
Sideboards have been implemented for Human players. We currently have:
* Sideboard creation support in relevant deck editor modes.
* In-game sideboarding with persistence between rounds in a match and sorting of cards in the in-game sideboard editor.
* Sideboard supported as a zone, with some relevant cards already in.
* Correct validation of decks, both before the game starts and between the rounds (Limited min 40, Constructed min 60, free-form sideboard/main in Draft and Sealed, 1:1 sideboarding with 0 or 15 cards allowed in sideboard in Constructed (all variants) and Quest; OK to have less than minimum between rounds in a match in all modes if lost cards on ante).
* Correct (fingers crossed) interaction of sideboarding with other relevant aspects of Forge rule enforcement (mulligan and ante interactions were corrected, initial hand and library between rounds were both corrected, everything else looks so far so good).
We don't yet have:
* AI sideboarding.
The deck conformance/legality limitation:
-----------------------------------------
The deck conformance/legality is now a user-toggable preference and is enabled by default. You no longer need to turn on dev mode to play an illegal deck.
Using Forge on displays that are only 600 pixels tall or slightly larger:
-------------------------------------------------------------------------
The "Sanctioned Format: Constructed" view should now be compatible with displays that are only 600 pixels tall. The deck list at 600 pixels tall should now display three lines of text rather than less than a single line of text.
We are looking for help in finding additional sound files for the new sound feature:
------------------------------------------------------------------------------------
This version of forge includes a few sound files for the new sound effect system. While we have several sounds assigned to a few of the available events there are a number of events that do not yet have a assigned sound file. This should be considered a work in progress and we could use some help in finding interesting sounds that we can add to forge.
The sound files need to be in wav or au format, wav appears to be more widespread but the code can handle either format. The sound files need to be copyright-free and they should be in the public domain.
You can either record your own sounds if you have the necessary equipment or you may be able to find an appropriate sound on a website such as http://www.freesound.org/browse/
You should note that sound files can be large and we would like to avoid this if possible. A good size to shoot for would be 50 K or less. There is a freeware sound editor that may have versions for all operating systems. This app is named Audacity.
We have a forge forum topic at the Collectible Card Games Headquarters web site that is devoted to finding sounds for this new sound system. Please visit this topic and contribute a sound or two. We can use your help and assistance. :)
http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8570
Notes about the second Quest World, Jamuraa:
--------------------------------------------
A second Quest World, Jamuraa, has been added to Forge. When playing Quest mode, it is now possible to 'Travel' between the regular Quest environment and the two Worlds, Shandalar and Jamuraa, both of which include special formats, opponents and challenges. Or you can start a new Quest in any of them.
Like Shandalar, Jamuraa is a fantasy world. Its peaceful existence has recently been wrecked by a planar conjunction that shattered the barriers between Jamuraa and the infernal planes known as Jahim, Saqar, and Jahannam. The demon planeswalkers who rule those planes, and their hellish sister, Lilith, are now extending their influence over Jamuraa and gradually destroying the whole continent. Your task is to fight their minions and ultimately challenge the four demons - but beware, their destructive might is unfathomable!
From a technical perspective, the following sets are available to the player in Jamuraa:
5th Edition, Arabian Nights, Mirage, Visions, Weatherlight.
Jamuraa contains:
- 81 opponent decks, broken down as follows: 13 'easy' decks, 17 'medium' decks, 31 'hard' decks, and 20 'very hard' decks.
- 9 challenges, including the 4 demon planeswalkers (the 3 demon rulers and Lilith) and 5 other special scenarios set in Jamuraa. All challenges are repeatable. All are fairly hard, and the 4 demon challenges are especially fiendish.
For the most part, the opponent duel and challenge decks are built with the same format restrictions as your own cardpool, and some of the easiest opponent decks were in fact based on a limited cardpool. But there will be exceptions, especially in the hard/very hard decks and challenges, which can be more like Vintage/T1 decks than pure Mirage + 5th Edition decks. There will be older cards here and there, and maybe even a random Tempest card or two (although these are extremely scarce!).
Hint: if you find the later 'Vintage' opponent decks unfair or near-impossible to beat with your 5th Edition/Mirage block cards, you can Travel to Shandalar and collect some old power cards there, and then return to Jamuraa. Just remember to complete your challenges before traveling.
Information on the quest worlds format can be found in this topic:
http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=9258
New Deck Editor features with improved Filters:
-----------------------------------------------
Some work is being done on the various deck editors -- including the quest spell shop -- and we hope to add in additional features while improving the UI. Here is a quick tutorial on the new features:
FILTERS
The Filters tab has been removed and filters are now controlled from the Card Catalog tab. Pretty much everything that used to be a checkbox is now represented by a toggle button. The statistics labels at the top for colors and card types can now be clicked to toggle display of the related cards. Clicking the Total Cards label in the upper left will alternately set all filters on and off.
Text searches are done by just typing the the text box. The results will update as you type. Use the combo box to the right of the text box to set whether the search should be for cards with or without the specified text. The three toggle buttons to the right of the combo box allow you to specify where in the card the text should (or should not) match. Complex text searches, such as for goblin creatures with haste but not with kicker costs, are possible by stacking the search filters. Put the current search filter on the stack by selecting the "Add filter" button to the left of the text box and choosing the "Current text search" option. You can also use the keyboard shortcut Ctrl+Enter (Cmd+Enter on OSX). This will keep your current text search applied, but will clear the text box and allow you to input the next search filter. To perform the example goblin search above, you would:
1) Ensure "in" is displayed in the the combo box. Enter "goblin" in the text box and deselect Name and Text so that only Type is selected.
2) Hit Ctrl+Enter (Cmd+Enter on OSX). Notice the "Contains: 'goblin" in: type" filter appears below the text box.
3) Type "haste" in the text box. Deselect Type and select Text. Hit Ctrl+Enter.
4) Change the combo box from "in" to "not in". Type "kicker" in the text box.
The shown entries match your combined search filter. Click the little 'x' in the upper right corner of each search widget to remove that filter from the filter stack.
Format filters allow you to restrict the shown cards to those that are legal in a particular format. Select the "Add filter" button, hover over the "Format" submenu, and choose one of the defined formats. The filter will appear below the text box and will be combined with whatever other filters you have on the stack. Hover the mouse over the filter widget to see details on allowed sets and banned cards. Keep in mind that cards from other, non-listed sets may still appear in the results if they are just reprints of allowed cards.
Set filters are similar to format filters except that a window will come up with a grid of checkboxes so you can select exactly which sets you would like shown. There is a checkbox at the bottom (off by default) that will allow the filter to include cards reprinted in unselected sets, just like the format filter above. If you don't check this checkbox, only cards printed in the selected sets will be shown, allowing you to focus on just that set's version of a particular card. This is very useful in quest mode when determining which cards in a set you have yet to collect.
Value range filters allow you to restrict the cards by power, toughness, and/or converted mana cost (CMC). The text boxes that appear in the filter widget are editable and update the shown cards in realtime as you modify the numbers.
Quest World filters are similar to Format filters in that they restrict the shown cards to a group of sets, respecting lists of banned cards. They are useful when constructing decks that will be valid in the various quest worlds. You can have more than one quest world filter active at the same time; useful if you are constructing a deck that will be used in multiple worlds.
SPELL SHOP
The spell shop interface has also received some usability enhancements. The first one you may notice is the addition of a new column named "Owned". This column is intended to help players decide whether buying an item is likely to be beneficial. The data in this column varies depending on what kind of item the row represents:
Cards: A number indicating how many of a particular card a player already owns
Preconstructed decks: A "YES" or "NO" indicating whether the deck exists in the player's deck list
Booster/fat packs: A percentage indicating how close a player is to completing the related set. "Complete" means at least one of every basic land and at least 4 of every other card.
If you don't want this column, it can be turned off in the editor preferences.
The new "Sell excess cards" button appears above the player's library. Clicking it will sell all cards that are not basic lands until only four copies of the cards remain. It's a one-click "cleanup" of the library and a great way to safely and quickly regain some cash.
The "Full catalog view" button appears to the left of the "Buy Card" button. Toggling this button will switch between showing the store's inventory and the full catalog. By applying a filter to show only a particular set (or group of sets), players can use this view to discover exactly which cards they do not own. Buying and selling cards is disabled while in this view.
Multibuy: By selecting any number of items and hitting space (or selecting the "Buy Card" or "Sell Card" buttons), a player can buy one of everything selected.
Find-as-you-type is now implemented for Deck Editor tables. Just start typing while the table has focus and the next card with a matching string in its name will be highlighted. If more than one card matches, hit Enter to select the next matching card. A popup panel will appear with the search string so you know what you are searching for. If no cards match the string, the string will be highlighted in red. Normally, if you hit the spacebar while a card is selected in one table, the card will be moved to the other table (catalog/deck). When the popup is displayed, space characters are interpreted as part of the search string. Find-as-you-type mode is automatically exited after 5 seconds of inactivity, or hit Escape to exit find-as-you-type mode immediately.
The Deck Editor has also gained some hotkey and context menu abilities. R-click on a card (or a group of selected cards) for a list of actions and keyboard shortcuts. In particular, you can now transfer cards 4 at a time using the keyboard and interact with the sideboard from anywhere. Also remember that you can jump to the other table with the arrow keys and jump to the text filter with ctrl/cmd+f. From the text filter, you can jump down to the tables by pressing enter.
The Game Log:
-------------
Added a 'copy to clipboard' button on WinLose screen so players can easily copy the game log.
The UI is more keyboard-friendly:
---------------------------------
Work was also done on making the UI more keyboard-friendly. For example, the OK button should now stay focused during matches, so you can advance through the stages by hitting Enter without having to go over and click the button all the time. If you find the button is losing focus, please report it as a bug.
Gatecrash Guild Sealed game mode:
---------------------------------
Gatecrash Guild Sealed game mode has been added. To use it, start a new Sealed Mode Game, select "Block / Set" and "Gatecrash Guild Sealed". Select the first (default) configuration in the "Choose Set Combination" dialog, and when asked to pick your boosters, choose the guild you want twice (once for the guild-specific booster, and then for the extra promo cards).
The following cards are not included in the guild boosters of this game mode because they are not currently implemented in Forge: Bane Alley Broker, Bioshift, Killing Glare, Simic Manipulator.
New User Preferences:
---------------------
The display of clones and copied cards is now a matter of user preference. A user to now choose whether a clone/copied card should use its native artwork or the artwork of the card being cloned. So if Infinite Reflection is played and the "Clones use original card art" preference is checked, the cards that become copies of the enchanted card will still use their own art whereas by default they would all use the card art from the enchanted card.
Flippable Cards:
----------------
Flippable cards -- cards that have an alternate card printed upside-down at the bottom -- will now flip their appearance on the battlefield when in a flipped state. They are now also flippable in the card viewer by clicking on the card image, just like double-sided "transform" cards have been. When flipped this way, the details of the flipped side can be examined in the Card Details oracle text.
High Quality Booster Pictures:
------------------------------
Forge will now download high quality booster pictures. If you are interested in these high quality booster pictures, then you should delete the pictures that are found in your <cacheDir>/pics/boosters/ directory. Then download the new pictures by using the Home View -> Game Settings -> Content Downloaders -> Download Quest Images button.
Flip, Transform and Morph cards:
--------------------------------
When you mouse over a flip, transform or Morph (controlled by you) card in battlefield, you may hold SHIFT to see other state of that card at the side panel that displays card picture and details.
Alternate sound system:
-----------------------
Implemented an alternative sound system for people who have issues with sound gradually or instantly disappearing on certain Linux systems. Can be switched on/off in the preferences without needing a restart. Uses the standard Java Sound API so it doesn't require an external dependency. It's non-caching and, as such, less efficient than the regular sound system, so only use it in case you have issues with the default system, otherwise leave the option unchecked.
Human vs Human play over the internet:
--------------------------------------
Some initial code has been added that will at some point in the future allow human vs human play over the internet. This is a work in progress and is far from being implemented at this time. Please be patient. Anyone who is curious can read the messages in the http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=9837 topic.
Random Deck generation:
-----------------------
Deck generation is now strictly conforming the colors chosen. You won't get any Boros Reckoner in a RG deck, that could be added before the change (because its manacost could be paid with red mana). Avacyn's Piligrim won't be added in a deck that has green but doesn't have white, though it does not consume, but produces white mana. As well there won't be added any creatures whose activated abilities require colors not choosen for a given deck. That is to say that now color identity is used for deck generation, that allows a better filtering of cards.
Single declare attackers step:
------------------------------
Combined declare attackers step for all Defending Players/Planeswalkers. On declare attackers step you have to click the entiry you are about to attack and then click on the creatures that should attack it. Clicking on a planeswalker or player visually highlights it, so that you will see whos attackers are assigned at the moment. By default your first opponent is pre-selected.
Booster slots:
--------------
Booster slots are now way more customizable. This change allows us to implement DGM boosters correctly. Note that custom cube .draft and .sealed files have changed their format. They require a booster description instead of Num(Rarity) N lines. Find example records in files describing SkieraCube and Juzamjedi cude coming with Forge. Singleton parameter is obsolette, since all cards within a booster slot are unique. Ignore rarity is also deprecated, for Booster options allow you to pick cards regardless of rarity with 'any' word standing for ignored rarity. (ex: 15 Any)
Quest challenge start in play cards:
------------------------------------
We have received reports that the quest challenge start in play cards are not appearing on the battlefield for people who were playing the classic mode rather than the fantasy mode. This should now be fixed.
Commander:
----------
We are taking baby steps toward Commander but there are some hurdles left before we get there. We need to implement the replacement effect that moves the commander back to your command zone; the cost-changing depending on how many times you've cast your commander; generating colorless mana if you try to generate mana outside your commanders color identity and AI for all the above.
Customize your Sealed Deck games with fantasy blocks:
-----------------------------------------------------
We have an informative topic at CCGH which explains how you can create "fantasy blocks" and the greatly increased flexibility you have when you are building your own blocks. Please see the topic http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8164 for more information. Also note that the syntax was recently changed, see message http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8164&view=unread#p117389 for additional information.
Custom Cube:
------------
1. You can create and save your cube as a constructed deck. Place the .dck file in the /res/cube directory.
2. To create a description file in the /res/draft folder you need to make a copy of any existing *.draft file and the adjust the deckfile and booster structure to meet your needs.
This file format is outdated. "NumCards" is no longer used. Instead there should be a line describing a booster.
Find some examples below:
Booster: 1 Rare, 11 Common, 3 Uncommon
Booster: 5 Common, 5 Uncommon, 5 Rare, 1 Mythic
Booster: 16 Any
Booster: 10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand
Blank screen when starting a match:
-----------------------------------
This problem should be fixed. At least you'll see an exception/crash report describing why the match didn't start instead of a blank screen.
Duplicate triggers problem solved:
----------------------------------
This problem has now been fixed. (Mana Vortex required 2 lands, Emrakul gave 2 extra turns, Empty the Warrens creatures two Storm triggers instead of one.)
Constructed mode AI vs AI matches:
----------------------------------
Added support for AI vs AI matches. There is a checkbox which allows you to view the AI vs AI match. This is a work in progress and is not finished. While it appears to work OK there is still a need for a few UI changes. Please be patient. These AI vs AI matches will not be a good way to test out various deck types against one another as the AI does not understand card combos and the AI is still limited at this time.
Constructed mode Human vs Human Hotseat matches:
------------------------------------------------
Hotseat matches are now possible. This mode has some known bugs to fix. At this time both players must use the same computer. Human vs Human matches via the internet is planned and should become available sometime in the future. Please be patient.
Using predetermined decks in quest mode Challenges:
---------------------------------------------------
Added the capability to play Challenges vs predetermined decks (along with a few other related options to disallow specific quest mode things). Added Sorin vs Tibalt, and Tibalt vs Sorin as examples of Challenges that force you to use a specific Deck. (They seemed to be the best duel deck compatibility for the AI).
Targeting Overlay:
------------------
Targeting arrows will now be shown for equipments equipping permanents currently under opponent's control (for those rare cases when e.g. an equipped creature gets Switcheroo'd for something else).
Our Lawyers Made Us Do This:
----------------------------
This product includes software developed by the Indiana University Extreme! Lab (http://www.extreme.indiana.edu/).

9
forge-ai/.classpath Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/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.testng.TESTNG_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

23
forge-ai/.project Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>forge-ai</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1
encoding//src/test/java=ISO-8859-1
encoding/<project>=ISO-8859-1

View File

@@ -0,0 +1,5 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7

View File

@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

28
forge-ai/pom.xml Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.5.13</version>
</parent>
<artifactId>forge-ai</artifactId>
<name>Forge AI</name>
<dependencies>
<dependency>
<groupId>forge</groupId>
<artifactId>forge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>forge</groupId>
<artifactId>forge-game</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -15,27 +15,27 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.ai;
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.CounterType;
import forge.GameEntity;
import forge.card.trigger.Trigger;
import forge.card.trigger.TriggerType;
import forge.game.Game;
import forge.game.phase.Combat;
import forge.game.phase.CombatUtil;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
/**
@@ -52,18 +52,18 @@ public class AiAttackController {
private final List<Card> attackers;
private final List<Card> blockers;
private final Random random = MyRandom.getRandom();
private final int randomInt = this.random.nextInt();
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> myList; // holds computer creatures
private final Player ai;
private final Player opponent;
private Player defendingOpponent;
private int aiAggression = 0; // added by Masher, how aggressive the ai is
// attack will be depending on circumstances
/**
* <p>
@@ -75,23 +75,36 @@ public class AiAttackController {
* @param possibleBlockers
* a {@link forge.CardList} object.
*/
public AiAttackController(final Player ai, final Player opponent) {
public AiAttackController(final Player ai) {
this.ai = ai;
this.opponent = opponent;
this.defendingOpponent = choosePreferredDefenderPlayer();
this.oppList = opponent.getCreaturesInPlay();
this.oppList = Lists.newArrayList();
this.oppList.addAll(this.defendingOpponent.getCreaturesInPlay());
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<Card>();
for (Card c : myList) {
if (CombatUtil.canAttack(c, opponent)) {
if (CombatUtil.canAttack(c, this.defendingOpponent)) {
attackers.add(c);
}
}
this.blockers = this.getPossibleBlockers(oppList, this.attackers);
} // constructor
/** Choose opponent for AI to attack here. Expand as necessary. */
private Player choosePreferredDefenderPlayer() {
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
return defender;
} else { //Otherwise choose a random opponent to ensure no ganging up on players
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
}
return defender;
}
/**
* <p>
* sortAttackers.
@@ -109,6 +122,7 @@ public class AiAttackController {
for (final Trigger trigger : attacker.getTriggers()) {
if (trigger.getMode() == TriggerType.Attacks) {
list.add(attacker);
break;
}
}
}
@@ -129,9 +143,9 @@ public class AiAttackController {
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* a {@link forge.game.card.Card} object.
* @param combat
* a {@link forge.game.phase.Combat} object.
* a {@link forge.game.combat.Combat} object.
* @return a boolean.
*/
public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat) {
@@ -141,14 +155,14 @@ public class AiAttackController {
return false;
}
final Player opp = ai.getOpponent();
final Player opp = this.defendingOpponent;
if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat) > 0) {
return true;
}
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
return true;
}
if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted")) {
if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted") && ComputerUtilCombat.predictDamageTo(opp, 1, attacker, true) > 0) {
return true;
}
@@ -192,7 +206,7 @@ public class AiAttackController {
* </p>
*
* @param c
* a {@link forge.Card} object.
* a {@link forge.game.card.Card} object.
* @param attackers
* a {@link forge.CardList} object.
* @return a boolean.
@@ -221,7 +235,7 @@ public class AiAttackController {
* @param attackers
* a {@link forge.CardList} object.
* @param combat
* a {@link forge.game.phase.Combat} object.
* a {@link forge.game.combat.Combat} object.
* @return a {@link forge.CardList} object.
*/
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
@@ -277,7 +291,7 @@ public class AiAttackController {
return notNeededAsBlockers;
}
final Player opp = ai.getOpponent();
final Player opp = this.defendingOpponent;
// Increase the total number of blockers needed by 1 if Finest Hour in
// play
@@ -343,7 +357,7 @@ public class AiAttackController {
if (!CombatUtil.canAttackNextTurn(attacker)) {
continue;
}
if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker)) {
if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker, ai)) {
blockersLeft--;
continue;
}
@@ -385,19 +399,19 @@ public class AiAttackController {
final List<Card> unblockedAttackers = new ArrayList<Card>();
final List<Card> remainingAttackers = new ArrayList<Card>(this.attackers);
final List<Card> remainingBlockers = new ArrayList<Card>(this.blockers);
final Player opp = ai.getOpponent();
final Player opp = this.defendingOpponent;
for (int i = 0; i < this.attackers.size(); i++) {
if (!CombatUtil.canBeBlocked(this.attackers.get(i), this.blockers)
|| this.attackers.get(i).hasKeyword("You may have CARDNAME assign its combat damage as though"
+ " it weren't blocked.")) {
unblockedAttackers.add(this.attackers.get(i));
for (Card attacker : attackers) {
if (!CombatUtil.canBeBlocked(attacker, this.blockers, null)
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
unblockedAttackers.add(attacker);
}
}
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.")) {
for (Card attacker : this.attackers) {
if (CombatUtil.canBlock(attacker, blocker)) {
remainingAttackers.remove(attacker);
@@ -440,64 +454,78 @@ public class AiAttackController {
* </p>
*
* @param c
* a {@link forge.game.phase.Combat} object.
* a {@link forge.game.combat.Combat} object.
* @param bAssault
* a boolean.
*/
public final GameEntity chooseDefender(final Combat c, final Combat gameCombat, final boolean bAssault) {
private final GameEntity chooseDefender(final Combat c, final boolean bAssault) {
final List<GameEntity> defs = c.getDefenders();
// Start with last planeswalker
int n = defs.size() - 1;
if (defs.size() == 1) {
return defs.get(0);
}
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0));
final GameEntity entity = ai.getMustAttackEntity();
if (null != entity) {
final List<GameEntity> defenders = gameCombat.getDefenders();
n = defenders.indexOf(entity);
int n = defs.indexOf(entity);
if (-1 == n) {
System.out.println("getMustAttackEntity() returned something not in defenders.");
return defs.get(0);
return prefDefender;
} else {
return entity;
}
} else {
return defs.get(bAssault ? 0 : n);
// 1. assault the opponent if you can kill him
if (bAssault) {
return prefDefender;
}
// 2. attack planeswalkers
List<Card> pwDefending = c.getDefendingPlaneswalkers();
if (!pwDefending.isEmpty()) {
return pwDefending.get(0);
} else {
return prefDefender;
}
}
}
final boolean LOG_AI_ATTACKS = false;
public final List<Player> getDefendingPlayers(Combat combat) {
final List<Player> defending = new ArrayList<Player>();
for (final GameEntity o : combat.getDefenders()) {
if (o instanceof Player) {
defending.add((Player) o);
}
}
return defending;
}
/**
* <p>
* Getter for the field <code>attackers</code>.
* </p>
*
* @return a {@link forge.game.phase.Combat} object.
* @return a {@link forge.game.combat.Combat} object.
*/
public final Combat getAttackers() {
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
Game game = ai.getGame();
this.random.setSeed(game.getPhaseHandler().getTurn() + this.randomInt);
final Combat combat = new Combat();
combat.setAttackingPlayer(game.getCombat().getAttackingPlayer());
game.getCombat().initiatePossibleDefenders(opponent);
combat.setDefenders(game.getCombat().getDefenders());
random.setSeed(ai.getGame().getPhaseHandler().getTurn() + AiAttackController.randomInt);
if (this.attackers.isEmpty()) {
return combat;
return;
}
final boolean bAssault = this.doAssault(ai);
// Determine who will be attacked
GameEntity defender = this.chooseDefender(combat, game.getCombat(), bAssault);
GameEntity defender = this.chooseDefender(combat, bAssault);
List<Card> attackersLeft = new ArrayList<Card>(this.attackers);
// Attackers that don't really have a choice
for (final Card attacker : this.attackers) {
@@ -507,22 +535,26 @@ public class AiAttackController {
boolean mustAttack = false;
for (String s : attacker.getKeyword()) {
if (s.equals("CARDNAME attacks each turn if able.")
|| s.equals("At the beginning of the end step, destroy CARDNAME.")
|| s.startsWith("CARDNAME attacks specific player each combat if able")) {
mustAttack = true;
break;
}
if ((s.equals("At the beginning of the end step, destroy CARDNAME.")
|| s.equals("At the beginning of the end step, exile CARDNAME.")
|| s.equals("At the beginning of the end step, sacrifice CARDNAME.")) {
|| s.equals("At the beginning of the end step, sacrifice CARDNAME."))
&& isEffectiveAttacker(ai, attacker, combat)) {
mustAttack = true;
break;
}
}
if (mustAttack || attacker.getSacrificeAtEOT()
|| attacker.getController().getMustAttackEntity() != null
if (mustAttack || attacker.getController().getMustAttackEntity() != null
|| attacker.getSVar("MustAttack").equals("True")) {
combat.addAttacker(attacker, defender);
attackersLeft.remove(attacker);
}
}
if (attackersLeft.isEmpty()) {
return combat;
return;
}
if (bAssault) {
if ( LOG_AI_ATTACKS )
@@ -533,7 +565,7 @@ public class AiAttackController {
combat.addAttacker(attacker, defender);
}
}
return combat;
return;
}
// Exalted
@@ -545,8 +577,7 @@ public class AiAttackController {
exalted = true;
break;
}
if (c.getName().equals("Finest Hour")
&& game.getPhaseHandler().isFirstCombat()) {
if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
exalted = true;
break;
}
@@ -566,7 +597,7 @@ public class AiAttackController {
for (Card attacker : this.attackers) {
if (CombatUtil.canAttack(attacker, defender, combat) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
combat.addAttacker(attacker, defender);
return combat;
return;
}
}
}
@@ -607,7 +638,7 @@ public class AiAttackController {
aiLifeToPlayerDamageRatio = (double) ai.getLife() / candidateCounterAttackDamage;
}
final Player opp = ai.getOpponent();
final Player opp = this.defendingOpponent;
// get the potential damage and strength of the AI forces
final List<Card> candidateAttackers = new ArrayList<Card>();
int candidateUnblockedDamage = 0;
@@ -770,11 +801,13 @@ public class AiAttackController {
attackersLeft = this.notNeededAsBlockers(ai, attackersLeft);
attackersLeft = this.sortAttackers(attackersLeft);
int iDefender = combat.getDefenders().indexOf(defender);
if ( LOG_AI_ATTACKS )
System.out.println("attackersLeft = " + attackersLeft);
for (int i = 0; i < attackersLeft.size(); i++) {
final Card attacker = attackersLeft.get(i);
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, ai.getOpponent())
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, this.defendingOpponent)
>= ComputerUtilCombat.getDamageToKill(attacker)) {
continue;
}
@@ -782,30 +815,39 @@ public class AiAttackController {
if (this.shouldAttack(ai, attacker, this.blockers, combat) && CombatUtil.canAttack(attacker, defender, combat)) {
combat.addAttacker(attacker, defender);
// check if attackers are enough to finish the attacked planeswalker
if (iDefender > 0) {
if (defender instanceof Card) {
Card pw = (Card) defender;
final int blockNum = this.blockers.size();
int attackNum = 0;
int damage = 0;
List<Card> attacking = combat.getAttackersByDefenderSlot(iDefender);
List<Card> attacking = combat.getAttackersOf(defender);
CardLists.sortByPowerAsc(attacking);
for (Card atta : attacking) {
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers)) {
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null);
} else if (CombatUtil.canBeBlocked(attacker, this.blockers)) {
} else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
attackNum++;
}
}
// if enough damage: switch to next planeswalker or player
if (damage >= pw.getCounters(CounterType.LOYALTY)) {
iDefender--;
defender = combat.getDefenders().get(iDefender);
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
boolean found = false;
// look for next planeswalker
for (Card walker : pwDefending) {
if (combat.getAttackersOf(walker).isEmpty()) {
defender = walker;
found = true;
break;
}
}
if (!found) {
defender = getDefendingPlayers(combat).get(0);
}
}
}
}
}
return combat;
} // getAttackers()
/**
@@ -832,7 +874,7 @@ public class AiAttackController {
* </p>
*
* @param c
* a {@link forge.Card} object.
* a {@link forge.game.card.Card} object.
* @return a int.
*/
public final int getAttack(final Card c) {
@@ -851,11 +893,11 @@ public class AiAttackController {
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* a {@link forge.game.card.Card} object.
* @param defenders
* a {@link forge.CardList} object.
* @param combat
* a {@link forge.game.phase.Combat} object.
* a {@link forge.game.combat.Combat} object.
* @return a boolean.
*/
public final boolean shouldAttack(final Player ai, final Card attacker, final List<Card> defenders, final Combat combat) {
@@ -934,9 +976,7 @@ public class AiAttackController {
return true;
}
if (numberOfPossibleBlockers > 1
|| (numberOfPossibleBlockers == 1
&& !attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures."))) {
if (numberOfPossibleBlockers > 1 || (numberOfPossibleBlockers == 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))) {
canBeBlocked = true;
}
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "

View File

@@ -0,0 +1,808 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.game.CardTraitBase;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* <p>
* ComputerUtil_Block2 class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class AiBlockController {
private final Player ai;
/** Constant <code>attackers</code>. */
private List<Card> attackers = new ArrayList<Card>(); // all attackers
/** Constant <code>attackersLeft</code>. */
private List<Card> attackersLeft = new ArrayList<Card>(); // keeps track of
// all currently
// unblocked
// attackers
/** Constant <code>blockedButUnkilled</code>. */
private List<Card> blockedButUnkilled = new ArrayList<Card>(); // blocked
// attackers
// that
// currently
// wouldn't be
// destroyed
/** Constant <code>blockersLeft</code>. */
private List<Card> blockersLeft = new ArrayList<Card>(); // keeps track of all
// unassigned
// blockers
private int diff = 0;
private boolean lifeInDanger = false;
public AiBlockController(Player aiPlayer) {
this.ai = aiPlayer;
}
// finds the creatures able to block the attacker
private List<Card> getPossibleBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft, final boolean solo) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card blocker : blockersLeft) {
// if the blocker can block a creature with lure it can't block a
// creature without
if (CombatUtil.canBlock(attacker, blocker, combat)) {
if (solo && blocker.hasKeyword("CARDNAME can't attack or block alone.")) {
continue;
}
blockers.add(blocker);
}
}
return blockers;
}
// finds blockers that won't be destroyed
private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card b : blockersLeft) {
if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false)) {
blockers.add(b);
}
}
return blockers;
}
// finds blockers that destroy the attacker
private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card b : blockersLeft) {
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false)) {
blockers.add(b);
}
}
return blockers;
}
private List<List<Card>> sortAttackerByDefender(final Combat combat) {
List<GameEntity> defenders = combat.getDefenders();
final ArrayList<List<Card>> attackers = new ArrayList<List<Card>>(defenders.size());
for (GameEntity defender : defenders) {
attackers.add(combat.getAttackersOf(defender));
}
return attackers;
}
private List<Card> sortPotentialAttackers(final Combat combat) {
final List<List<Card>> attackerLists = sortAttackerByDefender(combat);
final List<Card> sortedAttackers = new ArrayList<Card>();
final List<Card> firstAttacker = attackerLists.get(0);
final List<GameEntity> defenders = combat.getDefenders();
// Begin with the attackers that pose the biggest threat
ComputerUtilCard.sortByEvaluateCreature(firstAttacker);
CardLists.sortByPowerDesc(firstAttacker);
// If I don't have any planeswalkers than sorting doesn't really matter
if (defenders.size() == 1) {
return firstAttacker;
}
final boolean bLifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
// TODO Add creatures attacking Planeswalkers in order of which we want
// to protect
// defend planeswalkers with more loyalty before planeswalkers with less
// loyalty
// if planeswalker will be too difficult to defend don't even bother
for (List<Card> attacker : attackerLists) {
// Begin with the attackers that pose the biggest threat
CardLists.sortByPowerDesc(attacker);
for (final Card c : attacker) {
sortedAttackers.add(c);
}
}
if (bLifeInDanger) {
// add creatures attacking the Player to the front of the list
for (final Card c : firstAttacker) {
sortedAttackers.add(0, c);
}
} else {
// add creatures attacking the Player to the back of the list
for (final Card c : firstAttacker) {
sortedAttackers.add(c);
}
}
return sortedAttackers;
}
// ======================= block assignment functions
// ================================
// Good Blocks means a good trade or no trade
private void makeGoodBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless " +
"all creatures defending player controls block it.")) {
continue;
}
Card blocker = null;
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
List<Card> killingBlockers;
if (safeBlockers.size() > 0) {
// 1.Blockers that can destroy the attacker but won't get
// destroyed
killingBlockers = getKillingBlockers(combat, attacker, safeBlockers);
if (!killingBlockers.isEmpty()) {
blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
} else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
blockedButUnkilled.add(attacker);
}
} // no safe blockers
else {
// 3.Blockers that can destroy the attacker and have an upside when dying
killingBlockers = getKillingBlockers(combat, attacker, blockers);
for (Card b : killingBlockers) {
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0)
|| !b.getSVar("SacMe").equals("")) {
blocker = b;
break;
}
}
// 4.Blockers that can destroy the attacker and are worth less
if (blocker == null && !killingBlockers.isEmpty()) {
final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
int value = ComputerUtilCard.evaluateCreature(attacker);
// check for triggers when unblocked
for (Trigger trigger : attacker.getTriggers()) {
final Map<String, String> trigParams = trigger.getMapParams();
TriggerType mode = trigger.getMode();
if (!trigger.requirementsCheck(attacker.getGame())) {
continue;
}
if (mode == TriggerType.DamageDone) {
if ((!trigParams.containsKey("ValidSource")
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker))
&& attacker.getNetCombatDamage() > 0
&& (!trigParams.containsKey("ValidTarget")
|| CardTraitBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) {
value += 50;
}
} else if (mode == TriggerType.AttackerUnblocked) {
if (CardTraitBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) {
value += 50;
}
}
}
if ((ComputerUtilCard.evaluateCreature(worst) + diff) < value) {
blocker = worst;
}
}
}
if (blocker != null) {
currentAttackers.remove(attacker);
combat.addBlocker(attacker, blocker);
}
}
attackersLeft = (new ArrayList<Card>(currentAttackers));
}
// Good Gang Blocks means a good trade or no trade
/**
* <p>
* makeGangBlocks.
* </p>
*
* @param combat
* a {@link forge.game.combat.Combat} object.
* @return a {@link forge.game.combat.Combat} object.
*/
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
private void makeGangBlocks(final Combat combat) {
List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock));
List<Card> blockers;
// Try to block an attacker without first strike with a gang of first strikers
for (final Card attacker : attackersLeft) {
if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) {
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
final List<Card> firstStrikeBlockers = new ArrayList<Card>();
final List<Card> blockGang = new ArrayList<Card>();
for (int i = 0; i < blockers.size(); i++) {
if (blockers.get(i).hasFirstStrike() || blockers.get(i).hasDoubleStrike()) {
firstStrikeBlockers.add(blockers.get(i));
}
}
if (firstStrikeBlockers.size() > 1) {
CardLists.sortByPowerDesc(firstStrikeBlockers);
for (final Card blocker : firstStrikeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// if the total damage of the blockgang was not enough
// without but is enough with this blocker finish the
// blockgang
if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) < damageNeeded
|| CombatUtil.needsBlockers(attacker) > blockGang.size()) {
blockGang.add(blocker);
if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
currentAttackers.remove(attacker);
for (final Card b : blockGang) {
if (CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, b);
}
}
}
}
}
}
}
}
attackersLeft = (new ArrayList<Card>(currentAttackers));
currentAttackers = new ArrayList<Card>(attackersLeft);
// Try to block an attacker with two blockers of which only one will die
for (final Card attacker : attackersLeft) {
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
List<Card> usableBlockers;
final List<Card> blockGang = new ArrayList<Card>();
int absorbedDamage = 0; // The amount of damage needed to kill the first blocker
int currentValue = 0; // The value of the creatures in the blockgang
// AI can't handle good triple blocks yet
if (CombatUtil.needsBlockers(attacker) > 2) {
continue;
}
// 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
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike"))
&& !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) {
return false;
}
return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker);
}
});
if (usableBlockers.size() < 2) {
return;
}
final Card leader = ComputerUtilCard.getBestCreatureAI(usableBlockers);
blockGang.add(leader);
usableBlockers.remove(leader);
absorbedDamage = ComputerUtilCombat.getEnoughDamageToKill(leader, attacker.getNetCombatDamage(), attacker, true);
currentValue = ComputerUtilCard.evaluateCreature(leader);
for (final Card blocker : usableBlockers) {
// Add an additional blocker if the current blockers are not
// enough and the new one would deal the remaining damage
final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang);
final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker);
final int absorbedDamage2 = ComputerUtilCombat.getEnoughDamageToKill(blocker, attacker.getNetCombatDamage(), attacker, true);
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
&& !(damageNeeded > currentDamage + additionalDamage)
// The attacker will be killed
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
// only one blocker can be killed
|| currentValue + addedValue - 50 <= ComputerUtilCard.evaluateCreature(attacker)
// or attacker is worth more
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
// or life is in danger
&& CombatUtil.canBlock(attacker, blocker, combat)) {
// this is needed for attackers that can't be blocked by
// more than 1
currentAttackers.remove(attacker);
combat.addBlocker(attacker, blocker);
if (CombatUtil.canBlock(attacker, leader, combat)) {
combat.addBlocker(attacker, leader);
}
break;
}
}
}
attackersLeft = (new ArrayList<Card>(currentAttackers));
}
// Bad Trade Blocks (should only be made if life is in danger)
/**
* <p>
* makeTradeBlocks.
* </p>
*
* @param combat
* a {@link forge.game.combat.Combat} object.
* @return a {@link forge.game.combat.Combat} object.
*/
private void makeTradeBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
List<Card> killingBlockers;
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless " +
"all creatures defending player controls block it.")) {
continue;
}
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers);
if (!killingBlockers.isEmpty() && ComputerUtilCombat.lifeInDanger(ai, combat)) {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
combat.addBlocker(attacker, blocker);
currentAttackers.remove(attacker);
}
}
attackersLeft = (new ArrayList<Card>(currentAttackers));
}
// Chump Blocks (should only be made if life is in danger)
private void makeChumpBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
makeChumpBlocks(combat, currentAttackers);
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeMultiChumpBlocks(combat);
}
}
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
return;
}
Card attacker = attackers.get(0);
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || 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.")) {
attackers.remove(0);
makeChumpBlocks(combat, attackers);
return;
}
List<Card> chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
if (!chumpBlockers.isEmpty()) {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
// check if it's better to block a creature with lower power and without trample
if (attacker.hasKeyword("Trample")) {
final int damageAbsorbed = blocker.getLethalDamage();
if (attacker.getNetCombatDamage() > damageAbsorbed) {
for (Card other : attackers) {
if (other.equals(attacker)) {
continue;
}
if (other.getNetCombatDamage() >= damageAbsorbed
&& !other.hasKeyword("Trample")
&& CombatUtil.canBlock(other, blocker, combat)) {
combat.addBlocker(other, blocker);
attackersLeft.remove(other);
blockedButUnkilled.add(other);
attackers.remove(other);
makeChumpBlocks(combat, attackers);
return;
}
}
}
}
combat.addBlocker(attacker, blocker);
attackersLeft.remove(attacker);
blockedButUnkilled.add(attacker);
}
attackers.remove(0);
makeChumpBlocks(combat, attackers);
}
// Block creatures with "can't be blocked except by two or more creatures"
private void makeMultiChumpBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size(), combat)) {
continue;
}
List<Card> usedBlockers = new ArrayList<Card>();
for (Card blocker : possibleBlockers) {
if (CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
usedBlockers.add(blocker);
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) {
break;
}
}
}
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) {
attackersLeft.remove(attacker);
} else {
for (Card blocker : usedBlockers) {
combat.removeBlockAssignment(attacker, blocker);
}
}
}
}
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
private void reinforceBlockersAgainstTrample(final Combat combat) {
List<Card> chumpBlockers;
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, "Trample");
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced
// the keyword:
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
for (final Card attacker : tramplingAttackers) {
if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker))
|| 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.")) {
continue;
}
chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
chumpBlockers.removeAll(combat.getBlockers(attacker));
for (final Card blocker : chumpBlockers) {
// Add an additional blocker if the current blockers are not
// enough and the new one would suck some of the damage
if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker))
&& ComputerUtilCombat.shieldDamage(attacker, blocker) > 0
&& CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat.addBlocker(attacker, blocker);
}
}
}
}
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
private void reinforceBlockersToKill(final Combat combat) {
List<Card> safeBlockers;
List<Card> blockers;
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced
// the keyword:
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
for (final Card attacker : targetAttackers) {
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
blockers.removeAll(combat.getBlockers(attacker));
// Try to use safe blockers first
safeBlockers = getSafeBlockers(combat, attacker, blockers);
for (final Card blocker : safeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// Add an additional blocker if the current blockers are not
// enough and the new one would deal additional damage
if ((damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)))
&& ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
&& CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
}
blockers.remove(blocker); // Don't check them again next
}
// 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
if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) {
safeBlockers = CardLists.getKeyword(blockers, "First Strike");
safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike"));
} else {
safeBlockers = new ArrayList<Card>(blockers);
}
for (final Card blocker : safeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// Add an additional blocker if the current blockers are not
// enough and the new one would deal the remaining damage
final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker));
final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker);
if ((damageNeeded > currentDamage)
&& !(damageNeeded > (currentDamage + additionalDamage))
&& ((ComputerUtilCard.evaluateCreature(blocker) + diff) < ComputerUtilCard
.evaluateCreature(attacker)) && CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
blockersLeft.remove(blocker);
}
}
}
}
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
final List<Card> oldBlockers = combat.getAllBlockers();
for (final Card blocker : oldBlockers) {
if ( blocker.getController() == ai ) // don't touch other player's blockers
combat.removeFromCombat(blocker);
}
attackersLeft = new ArrayList<Card>(attackers); // keeps track of all currently unblocked attackers
blockersLeft = new ArrayList<Card>(possibleBlockers); // keeps track of all unassigned blockers
blockedButUnkilled = new ArrayList<Card>(); // keeps track of all blocked attackers that currently wouldn't be destroyed
}
/** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */
public void assignBlockers(final Combat combat) {
final List<Card> possibleBlockers = ai.getCreaturesInPlay();
attackers = sortPotentialAttackers(combat);
if (attackers.isEmpty()) {
return;
}
clearBlockers(combat, possibleBlockers);
List<Card> blockers;
List<Card> chumpBlockers;
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
// remove all attackers that can't be blocked anyway
for (final Card a : attackers) {
if (!CombatUtil.canBeBlocked(a, ai)) {
attackersLeft.remove(a);
}
}
// remove all blockers that can't block anyway
for (final Card b : possibleBlockers) {
if (!CombatUtil.canBlock(b, combat)) {
blockersLeft.remove(b);
}
}
if (attackersLeft.isEmpty()) {
return;
}
// Begin with the weakest blockers
CardLists.sortByPowerAsc(blockersLeft);
// == 1. choose best blocks first ==
makeGoodBlocks(combat);
makeGangBlocks(combat);
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade blocks
}
// if life is in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocks(combat); // choose necessary chump blocks
}
// if life is still in danger
// Reinforce blockers blocking attackers with trample if life is still
// in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
}
// Support blockers not destroying the attacker with more blockers to
// try to kill the attacker
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersToKill(combat);
}
// == 2. If the AI life would still be in danger make a safer approach ==
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
lifeInDanger = true;
clearBlockers(combat, possibleBlockers); // reset every block assignment
makeTradeBlocks(combat); // choose necessary trade blocks
// if life is in danger
makeGoodBlocks(combat);
// choose necessary chump blocks if life is still in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocks(combat);
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
}
makeGangBlocks(combat);
reinforceBlockersToKill(combat);
}
// == 3. If the AI life would be in serious danger make an even safer approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
clearBlockers(combat, possibleBlockers); // reset every block assignment
makeChumpBlocks(combat); // choose chump blocks
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade
}
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeGoodBlocks(combat);
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
else {
reinforceBlockersAgainstTrample(combat);
}
makeGangBlocks(combat);
// Support blockers not destroying the attacker with more blockers
// to try to kill the attacker
reinforceBlockersToKill(combat);
}
// assign blockers that have to block
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
// if an attacker with lure attacks - all that can block
for (final Card blocker : blockersLeft) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
chumpBlockers.add(blocker);
}
}
if (!chumpBlockers.isEmpty()) {
CardLists.shuffle(attackers);
for (final Card attacker : attackers) {
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat)
|| blocker.hasKeyword("CARDNAME blocks each turn if able."))) {
combat.addBlocker(attacker, blocker);
if (blocker.getMustBlockCards() != null) {
int mustBlockAmt = blocker.getMustBlockCards().size();
List<Card> blockedSoFar = combat.getAttackersBlockedBy(blocker);
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
blockersLeft.remove(blocker);
}
} else {
blockersLeft.remove(blocker);
}
}
}
}
}
}
public static List<Card> orderBlockers(Card attacker, List<Card> blockers) {
// ordering of blockers, sort by evaluate, then try to kill the best
int damage = attacker.getNetCombatDamage();
ComputerUtilCard.sortByEvaluateCreature(blockers);
final List<Card> first = new ArrayList<Card>();
final List<Card> last = new ArrayList<Card>();
for (Card blocker : blockers) {
int lethal = ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true);
if (lethal > damage) {
last.add(blocker);
} else {
first.add(blocker);
damage -= lethal;
}
}
first.addAll(last);
// TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures
// It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones
return first;
}
public static List<Card> orderAttackers(Card blocker, List<Card> attackers) {
// This shouldn't really take trample into account, but otherwise should be pretty similar to orderBlockers
// ordering of blockers, sort by evaluate, then try to kill the best
int damage = blocker.getNetCombatDamage();
ComputerUtilCard.sortByEvaluateCreature(attackers);
final List<Card> first = new ArrayList<Card>();
final List<Card> last = new ArrayList<Card>();
for (Card attacker : attackers) {
int lethal = ComputerUtilCombat.getEnoughDamageToKill(attacker, damage, blocker, true);
if (lethal > damage) {
last.add(attacker);
} else {
first.add(attacker);
damage -= lethal;
}
}
first.addAll(last);
// TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures
// It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones
return first;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,591 @@
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.card.CardType;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType;
import forge.game.cost.*;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AiCostDecision extends CostDecisionMakerBase implements ICostVisitor<PaymentDecision> {
private final SpellAbility ability;
private final Card source;
public AiCostDecision(Player ai0, SpellAbility sa) {
super(ai0);
ability = sa;
source = ability.getHostCard();
}
@Override
public PaymentDecision visit(CostAddMana cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostChooseCreatureType cost) {
String choice = player.getController().chooseSomeType("Creature", ability, new ArrayList<String>(CardType.getCreatureTypes()), new ArrayList<String>());
return PaymentDecision.type(choice);
}
@Override
public PaymentDecision visit(CostDiscard cost) {
final String type = cost.getType();
final List<Card> hand = player.getCardsIn(ZoneType.Hand);
if (type.equals("LastDrawn")) {
if (!hand.contains(player.getLastDrawnCard())) {
return null;
}
return PaymentDecision.card(player.getLastDrawnCard());
}
else if (cost.payCostFromSource()) {
if (!hand.contains(source)) {
return null;
}
return PaymentDecision.card(source);
}
else if (type.equals("Hand")) {
return PaymentDecision.card(hand);
}
if (type.contains("WithSameName")) {
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
if (type.equals("Random")) {
return PaymentDecision.card(CardLists.getRandomSubList(hand, c));
}
else {
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
}
}
@Override
public PaymentDecision visit(CostDamage cost) {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null; // cannot pay
} else {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
}
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostDraw cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostExile cost) {
if (cost.payCostFromSource()) {
return PaymentDecision.card(source);
}
if (cost.getType().equals("All")) {
return PaymentDecision.card(player.getCardsIn(cost.getFrom()));
}
else if (cost.getType().contains("FromTopGrave")) {
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
if (cost.getFrom().equals(ZoneType.Library)) {
return PaymentDecision.card(player.getCardsIn(ZoneType.Library, c));
}
else if (cost.sameZone) {
// TODO Determine exile from same zone for AI
return null;
}
else {
List<Card> chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
return null == chosen ? null : PaymentDecision.card(chosen);
}
}
@Override
public PaymentDecision visit(CostExiledMoveToGrave cost) {
Integer c = cost.convertAmount();
List<Card> chosen = new ArrayList<Card>();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<Card> typeList = player.getGame().getCardsIn(ZoneType.Exile);
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), player, source);
if (typeList.size() < c) {
return null;
}
CardLists.sortByPowerAsc(typeList);
Collections.reverse(typeList);
for (int i = 0; i < c; i++) {
chosen.add(typeList.get(i));
}
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
}
@Override
public PaymentDecision visit(CostFlipCoin cost) {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostGainControl cost) {
if (cost.payCostFromSource())
return PaymentDecision.card(source);
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
final List<Card> typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source);
if (typeList.size() < c) {
return null;
}
CardLists.sortByPowerAsc(typeList);
final List<Card> res = new ArrayList<Card>();
for (int i = 0; i < c; i++) {
res.add(typeList.get(i));
}
return res.isEmpty() ? null : PaymentDecision.card(res);
}
@Override
public PaymentDecision visit(CostGainLife cost) {
final List<Player> oppsThatCanGainLife = new ArrayList<Player>();
for (final Player opp : cost.getPotentialTargets(player, source)) {
if (opp.canGainLife()) {
oppsThatCanGainLife.add(opp);
}
}
if (oppsThatCanGainLife.size() == 0) {
return null;
}
return PaymentDecision.players(oppsThatCanGainLife);
}
@Override
public PaymentDecision visit(CostMill cost) {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<Card> topLib = player.getCardsIn(ZoneType.Library, c);
return topLib.size() < c ? null : PaymentDecision.card(topLib);
}
@Override
public PaymentDecision visit(CostPartMana cost) {
return PaymentDecision.number(0);
}
@Override
public PaymentDecision visit(CostPayLife cost) {
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
} else {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
}
if (!player.canPayLife(c)) {
return null;
}
// activator.payLife(c, null);
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostPutCardToLib cost) {
Integer c = cost.convertAmount();
final Game game = player.getGame();
List<Card> chosen = new ArrayList<Card>();
List<Card> list;
if (cost.isSameZone()) {
list = new ArrayList<Card>(game.getCardsIn(cost.getFrom()));
} else {
list = new ArrayList<Card>(player.getCardsIn(cost.getFrom()));
}
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
// Generalize cost
if (sVar.equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
list = CardLists.getValidCards(list, cost.getType().split(";"), player, source);
if (cost.isSameZone()) {
// Jotun Grunt
// TODO: improve AI
final List<Player> players = game.getPlayers();
for (Player p : players) {
List<Card> enoughType = CardLists.filter(list, CardPredicates.isOwner(p));
if (enoughType.size() >= c) {
chosen.addAll(enoughType);
break;
}
}
chosen = chosen.subList(0, c);
} else {
chosen = ComputerUtil.choosePutToLibraryFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
}
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
}
@Override
public PaymentDecision visit(CostPutCounter cost) {
if (cost.payCostFromSource()) {
return PaymentDecision.card(source);
}
final List<Card> typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source);
Card card = null;
if (cost.getType().equals("Creature.YouCtrl")) {
card = ComputerUtilCard.getWorstCreatureAI(typeList);
} else {
card = ComputerUtilCard.getWorstPermanentAI(typeList, false, false, false, false);
}
return PaymentDecision.card(card);
}
@Override
public PaymentDecision visit(CostTap cost) {
return PaymentDecision.number(0);
}
@Override
public PaymentDecision visit(CostTapType cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(amount);
if (sVar.equals("XChoice")) {
List<Card> typeList =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), ability.getActivatingPlayer(), ability.getHostCard());
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
c = typeList.size();
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
}
if (cost.getType().contains("sharesCreatureTypeWith") || cost.getType().contains("withTotalPowerGE")) {
return null;
}
List<Card> totap = ComputerUtil.chooseTapType(player, cost.getType(), source, !cost.canTapSource, c);
if (totap == null) {
System.out.println("Couldn't find a valid card to tap for: " + source.getName());
return null;
}
return PaymentDecision.card(totap);
}
@Override
public PaymentDecision visit(CostSacrifice cost) {
if (cost.payCostFromSource()) {
return PaymentDecision.card(source);
}
if (cost.getAmount().equals("All")) {
/*List<Card> typeList = new ArrayList<Card>(activator.getCardsIn(ZoneType.Battlefield));
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), activator, source);
if (activator.hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) {
typeList = CardLists.getNotType(typeList, "Creature");
}*/
// Does the AI want to use Sacrifice All?
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
return null;
}
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<Card> list = ComputerUtil.chooseSacrificeType(player, cost.getType(), source, ability.getTargetCard(), c);
return PaymentDecision.card(list);
}
@Override
public PaymentDecision visit(CostReturn cost) {
if (cost.payCostFromSource())
return PaymentDecision.card(source);
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<Card> res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c);
return res.isEmpty() ? null : PaymentDecision.card(res);
}
@Override
public PaymentDecision visit(CostReveal cost) {
final String type = cost.getType();
List<Card> hand = new ArrayList<Card>(player.getCardsIn(ZoneType.Hand));
if (cost.payCostFromSource()) {
if (!hand.contains(source)) {
return null;
}
return PaymentDecision.card(source);
}
if (cost.getType().equals("Hand"))
return PaymentDecision.card(player.getCardsIn(ZoneType.Hand));
if (cost.getType().equals("SameColor")) {
return null;
}
hand = CardLists.getValidCards(hand, type.split(";"), player, source);
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(cost.getAmount());
if (sVar.equals("XChoice")) {
c = hand.size();
} else {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
}
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
}
@Override
public PaymentDecision visit(CostRemoveAnyCounter cost) {
final String amount = cost.getAmount();
final int c = AbilityUtils.calculateAmount(source, amount, ability);
final String type = cost.getType();
List<Card> typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source);
List<Card> hperms = CardLists.filter(typeList, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
for (final CounterType c1 : CounterType.values()) {
if (crd.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, crd)) {
return true;
}
}
return false;
}
});
if(hperms.isEmpty())
return null;
PaymentDecision result = PaymentDecision.card(hperms);
Card valid = hperms.get(0);
for (CounterType c1 : valid.getCounters().keySet()) {
if (valid.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, valid)) {
result.ct = c1;
break;
}
}
// Only find cards with enough negative counters
// TODO: add ai for Chisei, Heart of Oceans
return result;
}
@Override
public PaymentDecision visit(CostRemoveCounter cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
final String type = cost.getType();
if (c == null) {
final String sVar = ability.getSVar(amount);
if (sVar.equals("XChoice")) {
return null;
}
if (amount.equals("All")) {
c = source.getCounters(cost.counter);
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
}
if (!cost.payCostFromSource()) {
List<Card> typeList;
if (type.equals("OriginalHost")) {
typeList = Lists.newArrayList(ability.getOriginalHost());
} else {
typeList = CardLists.getValidCards(player.getCardsIn(cost.zone), type.split(";"), player, source);
}
for (Card card : typeList) {
if (card.getCounters(cost.counter) >= c) {
return PaymentDecision.card(card, c);
}
}
return null;
}
if (c > source.getCounters(cost.counter)) {
System.out.println("Not enough " + cost.counter + " on " + source.getName());
return null;
}
return PaymentDecision.card(source, c);
}
@Override
public PaymentDecision visit(CostUntapType cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
if (c == null) {
final String sVar = ability.getSVar(amount);
if (sVar.equals("XChoice")) {
List<Card> typeList = player.getGame().getCardsIn(ZoneType.Battlefield);
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), player, ability.getHostCard());
if (!cost.canUntapSource) {
typeList.remove(source);
}
typeList = CardLists.filter(typeList, Presets.TAPPED);
c = typeList.size();
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
}
List<Card> list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c);
if (list == null) {
System.out.println("Couldn't find a valid card to untap for: " + source.getName());
return null;
}
return PaymentDecision.card(list);
}
@Override
public PaymentDecision visit(CostUntap cost) {
return PaymentDecision.number(0);
}
@Override
public PaymentDecision visit(CostUnattach cost) {
Card cardToUnattach = cost.findCardToUnattach(source, (Player) player, ability);
if (cardToUnattach == null) {
// We really shouldn't be able to get here if there's nothing to unattach
return null;
}
return PaymentDecision.card(cardToUnattach);
}
@Override
public boolean paysRightAfterDecision() {
return false;
}
}

View File

@@ -0,0 +1,20 @@
package forge.ai;
public enum AiPlayDecision {
WillPlay,
CantPlaySa,
CantPlayAi,
CantAfford,
CantAffordX,
WaitForMain2,
AnotherTime,
MissingNeededCards,
NeedsToPlayCriteriaNotMet,
TargetingFailed,
CostNotAcceptable,
WouldDestroyLegend,
WouldDestroyOtherPlaneswalker,
WouldBecomeZeroToughnessCreature,
WouldDestroyWorldEnchantment,
BadEtbEffects;
}

View File

@@ -1,187 +1,192 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2013 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.ai;
import forge.game.player.LobbyPlayer;
import forge.game.player.LobbyPlayerAi;
import forge.util.Aggregates;
import forge.util.FileUtil;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
/**
* Holds default AI personality profile values in an enum.
* Loads profile from the given text file when setProfile is called.
* If a requested value is not loaded from a profile, default is returned.
*
* @author Forge
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
*/
public class AiProfileUtil {
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
private static final String AI_PROFILE_DIR = "res/ai";
private static final String AI_PROFILE_EXT = ".ai";
public static final String AI_PROFILE_RANDOM_MATCH = "* Random (Match) *";
public static final String AI_PROFILE_RANDOM_DUEL = "* Random (Duel) *";
/** Builds an AI profile file name with full relative
* path based on the profile name.
* @param profileName the name of the profile.
* @return the full relative path and file name for the given profile.
*/
private static String buildFileName(final String profileName) {
return String.format("%s/%s%s", AI_PROFILE_DIR, profileName, AI_PROFILE_EXT);
}
/**
* Load all profiles
*/
public static final void loadAllProfiles() {
loadedProfiles.clear();
ArrayList<String> availableProfiles = getAvailableProfiles();
for (String profile : availableProfiles) {
loadedProfiles.put(profile, loadProfile(profile));
}
}
/**
* Load a single profile.
* @param profileName a profile to load.
*/
private static final Map<AiProps, String> loadProfile(final String profileName) {
Map<AiProps, String> profileMap = new HashMap<AiProps, String>();
List<String> lines = FileUtil.readFile(buildFileName(profileName));
for (String line : lines) {
if (line.startsWith("#") || (line.length() == 0)) {
continue;
}
final String[] split = line.split("=");
if (split.length == 2) {
profileMap.put(AiProps.valueOf(split[0]), split[1]);
} else if (split.length == 1 && line.endsWith("=")) {
profileMap.put(AiProps.valueOf(split[0]), "");
}
}
return profileMap;
}
/**
* Returns an AI property value for the current profile.
*
* @param fp0 an AI property.
* @return String
*/
public static String getAIProp(final LobbyPlayer p, final AiProps fp0) {
String val = null;
if (!(p instanceof LobbyPlayerAi))
return "";
String profile = ((LobbyPlayerAi) p).getAiProfile();
if (loadedProfiles.get(profile) != null) {
val = loadedProfiles.get(profile).get(fp0);
}
if (val == null) { val = fp0.getDefault(); }
return val;
}
/**
* Returns an array of strings containing all available profiles.
* @return ArrayList<String> - an array of strings containing all
* available profiles.
*/
public static ArrayList<String> getAvailableProfiles()
{
final ArrayList<String> availableProfiles = new ArrayList<String>();
final File dir = new File(AI_PROFILE_DIR);
final String[] children = dir.list();
if (children == null) {
System.err.println("AIProfile > can't find AI profile directory!");
} else {
for (int i = 0; i < children.length; i++) {
if (children[i].endsWith(AI_PROFILE_EXT)) {
availableProfiles.add(children[i].substring(0, children[i].length() - AI_PROFILE_EXT.length()));
}
}
}
return availableProfiles;
}
/**
* Returns an array of strings containing all available profiles including
* the special "Random" profiles.
* @return ArrayList<String> - an array list of strings containing all
* available profiles including special random profile tags.
*/
public static ArrayList<String> getProfilesDisplayList() {
final ArrayList<String> availableProfiles = new ArrayList<String>();
availableProfiles.add(AI_PROFILE_RANDOM_MATCH);
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
availableProfiles.addAll(getAvailableProfiles());
return availableProfiles;
}
/**
* Returns a random personality from the currently available ones.
* @return String - a string containing a random profile from all the
* currently available ones.
*/
public static String getRandomProfile() {
return Aggregates.random(getAvailableProfiles());
}
/**
* Simple class test facility for AiProfileUtil.
*-/
public static void selfTest() {
final LobbyPlayer activePlayer = Singletons.getControl().getPlayer().getLobbyPlayer();
System.out.println(String.format("Current profile = %s", activePlayer.getAiProfile()));
ArrayList<String> profiles = getAvailableProfiles();
System.out.println(String.format("Available profiles: %s", profiles));
if (profiles.size() > 0) {
System.out.println(String.format("Loading all profiles..."));
loadAllProfiles();
System.out.println(String.format("Setting profile %s...", profiles.get(0)));
activePlayer.setAiProfile(profiles.get(0));
for (AiProps property : AiProps.values()) {
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
}
String randomProfile = getRandomProfile();
System.out.println(String.format("Loading random profile %s...", randomProfile));
activePlayer.setAiProfile(randomProfile);
for (AiProps property : AiProps.values()) {
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
}
}
}
*/
}
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2013 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai;
import forge.game.player.LobbyPlayer;
import forge.util.Aggregates;
import forge.util.FileUtil;
import org.apache.commons.lang3.ArrayUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Holds default AI personality profile values in an enum.
* Loads profile from the given text file when setProfile is called.
* If a requested value is not loaded from a profile, default is returned.
*
* @author Forge
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
*/
public class AiProfileUtil {
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
private static final String AI_PROFILE_DIR = "res/ai";
private static final String AI_PROFILE_EXT = ".ai";
public static final String AI_PROFILE_RANDOM_MATCH = "* Random (Match) *";
public static final String AI_PROFILE_RANDOM_DUEL = "* Random (Duel) *";
/** Builds an AI profile file name with full relative
* path based on the profile name.
* @param profileName the name of the profile.
* @return the full relative path and file name for the given profile.
*/
private static String buildFileName(final String profileName) {
return String.format("%s/%s%s", AI_PROFILE_DIR, profileName, AI_PROFILE_EXT);
}
/**
* Load all profiles
*/
public static final void loadAllProfiles() {
loadedProfiles.clear();
ArrayList<String> availableProfiles = getAvailableProfiles();
for (String profile : availableProfiles) {
loadedProfiles.put(profile, loadProfile(profile));
}
}
/**
* Load a single profile.
* @param profileName a profile to load.
*/
private static final Map<AiProps, String> loadProfile(final String profileName) {
Map<AiProps, String> profileMap = new HashMap<AiProps, String>();
List<String> lines = FileUtil.readFile(buildFileName(profileName));
for (String line : lines) {
if (line.startsWith("#") || (line.length() == 0)) {
continue;
}
final String[] split = line.split("=");
if (split.length == 2) {
profileMap.put(AiProps.valueOf(split[0]), split[1]);
} else if (split.length == 1 && line.endsWith("=")) {
profileMap.put(AiProps.valueOf(split[0]), "");
}
}
return profileMap;
}
/**
* Returns an AI property value for the current profile.
*
* @param fp0 an AI property.
* @return String
*/
public static String getAIProp(final LobbyPlayer p, final AiProps fp0) {
String val = null;
if (!(p instanceof LobbyPlayerAi))
return "";
String profile = ((LobbyPlayerAi) p).getAiProfile();
if (loadedProfiles.get(profile) != null) {
val = loadedProfiles.get(profile).get(fp0);
}
if (val == null) { val = fp0.getDefault(); }
return val;
}
/**
* Returns an array of strings containing all available profiles.
* @return ArrayList<String> - an array of strings containing all
* available profiles.
*/
public static ArrayList<String> getAvailableProfiles()
{
final ArrayList<String> availableProfiles = new ArrayList<String>();
final File dir = new File(AI_PROFILE_DIR);
final String[] children = dir.list();
if (children == null) {
System.err.println("AIProfile > can't find AI profile directory!");
} else {
for (int i = 0; i < children.length; i++) {
if (children[i].endsWith(AI_PROFILE_EXT)) {
availableProfiles.add(children[i].substring(0, children[i].length() - AI_PROFILE_EXT.length()));
}
}
}
return availableProfiles;
}
/**
* Returns an array of strings containing all available profiles including
* the special "Random" profiles.
* @return ArrayList<String> - an array list of strings containing all
* available profiles including special random profile tags.
*/
public static ArrayList<String> getProfilesDisplayList() {
final ArrayList<String> availableProfiles = new ArrayList<String>();
availableProfiles.add(AI_PROFILE_RANDOM_MATCH);
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
availableProfiles.addAll(getAvailableProfiles());
return availableProfiles;
}
public static String[] getProfilesArray() {
return getProfilesDisplayList().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
}
/**
* Returns a random personality from the currently available ones.
* @return String - a string containing a random profile from all the
* currently available ones.
*/
public static String getRandomProfile() {
return Aggregates.random(getAvailableProfiles());
}
/**
* Simple class test facility for AiProfileUtil.
*-/
public static void selfTest() {
final LobbyPlayer activePlayer = Singletons.getControl().getPlayer().getLobbyPlayer();
System.out.println(String.format("Current profile = %s", activePlayer.getAiProfile()));
ArrayList<String> profiles = getAvailableProfiles();
System.out.println(String.format("Available profiles: %s", profiles));
if (profiles.size() > 0) {
System.out.println(String.format("Loading all profiles..."));
loadAllProfiles();
System.out.println(String.format("Setting profile %s...", profiles.get(0)));
activePlayer.setAiProfile(profiles.get(0));
for (AiProps property : AiProps.values()) {
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
}
String randomProfile = getRandomProfile();
System.out.println(String.format("Loading random profile %s...", randomProfile));
activePlayer.setAiProfile(randomProfile);
for (AiProps property : AiProps.values()) {
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
}
}
}
*/
}

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.ai;
package forge.ai;
/**
* AI personality profile settings identifiers, and their default values.
@@ -24,7 +24,12 @@ package forge.game.ai;
* from the text file.
*/
public enum AiProps { /** */
AI_MULLIGAN_THRESHOLD ("5"); /** */
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
MULLIGAN_THRESHOLD ("5"), /** */
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
CHEAT_WITH_MANA_ON_SHUFFLE ("FALSE"); /** */
private final String strDefaultVal;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,897 @@
package forge.ai;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.game.Game;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
import java.util.Map.Entry;
/**
* TODO: Write javadoc for this type.
*
*/
public class ComputerUtilCard {
/**
* <p>
* getMostExpensivePermanentAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param spell
* a {@link forge.game.card.Card} object.
* @param targeted
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getMostExpensivePermanentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
List<Card> all = list;
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
}
return ComputerUtilCard.getMostExpensivePermanentAI(all);
}
/**
* <p>
* Sorts a List<Card> by "best" using the EvaluateCreature function.
* the best creatures will be first in the list.
* </p>
*
* @param list
* a {@link forge.CardList} object.
*/
public static void sortByEvaluateCreature(final List<Card> list) {
Collections.sort(list, ComputerUtilCard.EvaluateCreatureComparator);
} // sortByEvaluateCreature()
// The AI doesn't really pick the best artifact, just the most expensive.
/**
* <p>
* getBestArtifactAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestArtifactAI(final List<Card> list) {
List<Card> all = CardLists.filter(list, CardPredicates.Presets.ARTIFACTS);
if (all.size() == 0) {
return null;
}
// get biggest Artifact
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
}
// The AI doesn't really pick the best enchantment, just the most expensive.
/**
* <p>
* getBestEnchantmentAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param spell
* a {@link forge.game.card.Card} object.
* @param targeted
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestEnchantmentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
List<Card> all = CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS);
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
}
// get biggest Enchantment
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
}
/**
* <p>
* getBestLandAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestLandAI(final Collection<Card> list) {
final List<Card> land = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (land.isEmpty()) {
return null;
}
// prefer to target non basic lands
final List<Card> nbLand = CardLists.filter(land, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
if (!nbLand.isEmpty()) {
// TODO - Rank non basics?
return Aggregates.random(nbLand);
}
// if no non-basic lands, target the least represented basic land type
String sminBL = "";
int iminBL = 20000; // hopefully no one will ever have more than 20000
// lands of one type....
int n = 0;
for (String name : MagicColor.Constant.BASIC_LANDS) {
n = CardLists.getType(land, name).size();
if ((n < iminBL) && (n > 0)) {
// if two or more are tied, only the
// first
// one checked will be used
iminBL = n;
sminBL = name;
}
}
if (iminBL == 20000) {
return null; // no basic land was a minimum
}
final List<Card> bLand = CardLists.getType(land, sminBL);
for (Card ut : Iterables.filter(bLand, CardPredicates.Presets.UNTAPPED)) {
return ut;
}
return Aggregates.random(bLand); // random tapped land of least represented type
}
/**
* <p>
* getCheapestPermanentAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param spell
* a {@link forge.game.card.Card} object.
* @param targeted
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getCheapestPermanentAI(Collection<Card> all, final SpellAbility spell, final boolean targeted) {
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
}
if (all.isEmpty()) {
return null;
}
// get cheapest card:
Card cheapest = null;
for (Card c : all) {
if (cheapest == null || cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
cheapest = c;
}
}
return cheapest;
}
// returns null if list.size() == 0
/**
* <p>
* getBestAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestAI(final Collection<Card> list) {
// Get Best will filter by appropriate getBest list if ALL of the list
// is of that type
if (Iterables.all(list, CardPredicates.Presets.CREATURES))
return ComputerUtilCard.getBestCreatureAI(list);
if (Iterables.all(list, CardPredicates.Presets.LANDS))
return getBestLandAI(list);
// TODO - Once we get an EvaluatePermanent this should call
// getBestPermanent()
return ComputerUtilCard.getMostExpensivePermanentAI(list);
}
/**
* getBestCreatureAI.
*
* @param list
* the list
* @return the card
*/
public static Card getBestCreatureAI(final Collection<Card> list) {
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.fnEvaluateCreature);
}
/**
* <p>
* getWorstCreatureAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstCreatureAI(final Collection<Card> list) {
return Aggregates.itemWithMin(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.fnEvaluateCreature);
}
// This selection rates tokens higher
/**
* <p>
* getBestCreatureToBounceAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestCreatureToBounceAI(final List<Card> list) {
final int tokenBonus = 60;
Card biggest = null;
int biggestvalue = -1;
for(Card card : CardLists.filter(list, CardPredicates.Presets.CREATURES)) {
int newvalue = ComputerUtilCard.evaluateCreature(card);
newvalue += card.isToken() ? tokenBonus : 0; // raise the value of tokens
if (biggestvalue < newvalue) {
biggest = card;
biggestvalue = newvalue;
}
}
return biggest;
}
/**
* <p>
* getWorstAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstAI(final Collection<Card> list) {
return ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false);
}
/**
* <p>
* getWorstPermanentAI.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @param biasEnch
* a boolean.
* @param biasLand
* a boolean.
* @param biasArt
* a boolean.
* @param biasCreature
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstPermanentAI(final Collection<Card> list, final boolean biasEnch, final boolean biasLand,
final boolean biasArt, final boolean biasCreature) {
if (list.size() == 0) {
return null;
}
final boolean hasEnchantmants = Iterables.any(list, CardPredicates.Presets.ENCHANTMENTS);
if (biasEnch && hasEnchantmants) {
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS), null, false);
}
final boolean hasArtifacts = Iterables.any(list, CardPredicates.Presets.ARTIFACTS);
if (biasArt && hasArtifacts) {
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ARTIFACTS), null, false);
}
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
return ComputerUtilCard.getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
}
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
if (biasCreature && hasCreatures) {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
}
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (lands.size() > 6) {
return ComputerUtilCard.getWorstLand(lands);
}
if (hasEnchantmants || hasArtifacts) {
final List<Card> ae = CardLists.filter(list, Predicates.<Card>or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS));
return getCheapestPermanentAI(ae, null, false);
}
if (hasCreatures) {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
}
// Planeswalkers fall through to here, lands will fall through if there
// aren't very many
return getCheapestPermanentAI(list, null, false);
}
public static final Function<Card, Integer> fnEvaluateCreature = new Function<Card, Integer>() {
@Override
public Integer apply(Card a) {
return ComputerUtilCard.evaluateCreature(a);
}
};
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
@Override
public int compare(final Card a, final Card b) {
return ComputerUtilCard.evaluateCreature(b) - ComputerUtilCard.evaluateCreature(a);
}
};
/**
* <p>
* evaluateCreature.
* </p>
*
* @param c
* a {@link forge.game.card.Card} object.
* @return a int.
*/
public static int evaluateCreature(final Card c) {
int value = 100;
if (c.isToken()) {
value = 80; // tokens should be worth less than actual cards
}
int power = c.getNetCombatDamage();
final int toughness = c.getNetDefense();
for (String keyword : c.getKeyword()) {
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.")
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
power = 0;
break;
}
}
value += power * 15;
value += toughness * 10;
value += c.getCMC() * 5;
// Evasion keywords
if (c.hasKeyword("Flying")) {
value += power * 10;
}
if (c.hasKeyword("Horsemanship")) {
value += power * 10;
}
if (c.hasKeyword("Unblockable")) {
value += power * 10;
} else {
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
value += power * 6;
}
if (c.hasKeyword("Fear")) {
value += power * 6;
}
if (c.hasKeyword("Intimidate")) {
value += power * 6;
}
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
value += power * 3;
}
}
// Other good keywords
if (power > 0) {
if (c.hasKeyword("Double Strike")) {
value += 10 + (power * 15);
} else if (c.hasKeyword("First Strike")) {
value += 10 + (power * 5);
}
if (c.hasKeyword("Deathtouch")) {
value += 25;
}
if (c.hasKeyword("Lifelink")) {
value += power * 10;
}
if (power > 1 && c.hasKeyword("Trample")) {
value += (power - 1) * 5;
}
if (c.hasKeyword("Vigilance")) {
value += (power * 5) + (toughness * 5);
}
if (c.hasKeyword("Wither")) {
value += power * 10;
}
if (c.hasKeyword("Infect")) {
value += power * 15;
}
value += c.getKeywordMagnitude("Rampage");
}
value += c.getKeywordMagnitude("Bushido") * 16;
value += c.getAmountOfKeyword("Flanking") * 15;
value += c.getAmountOfKeyword("Exalted") * 15;
value += c.getKeywordMagnitude("Annihilator") * 50;
// Defensive Keywords
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
value += 5;
}
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
value += 3;
}
// Protection
if (c.hasKeyword("Indestructible")) {
value += 70;
}
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
value += 60;
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
value += 50;
}
if (c.hasKeyword("Hexproof")) {
value += 35;
} else if (c.hasKeyword("Shroud")) {
value += 30;
}
if (c.hasStartOfKeyword("Protection")) {
value += 20;
}
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
value += 10;
}
value += c.getKeywordMagnitude("Absorb") * 11;
// Bad keywords
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
value -= (power * 9) + 40;
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
value -= 40;
}
if (c.hasKeyword("CARDNAME can't block.")) {
value -= 10;
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")) {
value -= 10;
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
value -= 10;
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
value -= toughness * 5;
}
if (c.hasSVar("DestroyWhenDamaged")) {
value -= (toughness - 1) * 9;
}
if (c.hasKeyword("CARDNAME can't attack or block.")) {
value = 50 + (c.getCMC() * 5); // reset everything - useless
}
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
if (c.isTapped()) {
value = 50 + (c.getCMC() * 5); // reset everything - useless
} else {
value -= 50;
}
}
if (c.hasKeyword("At the beginning of the end step, destroy CARDNAME.")
|| c.hasKeyword("At the beginning of the end step, exile CARDNAME.")
|| c.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")) {
value -= 50;
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
value -= 30;
} else if (c.hasStartOfKeyword("At the beginning of your upkeep, sacrifice CARDNAME unless you pay")) {
value -= 20;
} else if (c.hasStartOfKeyword("(Echo unpaid)")) {
value -= 10;
}
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= 20;
}
if (c.hasStartOfKeyword("Fading")) {
value -= 20;
}
if (c.hasStartOfKeyword("Vanishing")) {
value -= 20;
}
if (c.getSVar("Targeting").equals("Dies")) {
value -= 25;
}
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility()) {
value += 10;
}
}
if (!c.getManaAbility().isEmpty()) {
value += 10;
}
if (c.isUntapped()) {
value += 1;
}
// paired creatures are more valuable because they grant a bonus to the other creature
if (c.isPaired()) {
value += 14;
}
if (!c.getEncoded().isEmpty()) {
value += 24;
}
return value;
} // evaluateCreature
/**
* <p>
* evaluatePermanentList.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a int.
*/
public static int evaluatePermanentList(final List<Card> list) {
int value = 0;
for (int i = 0; i < list.size(); i++) {
value += list.get(i).getCMC() + 1;
}
return value;
}
/**
* <p>
* evaluateCreatureList.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a int.
*/
public static int evaluateCreatureList(final List<Card> list) {
return Aggregates.sum(list, fnEvaluateCreature);
}
/**
* <p>
* doesCreatureAttackAI.
* </p>
*
* @param ai
* the AI player
* @param card
* a {@link forge.game.card.Card} object.
* @return a boolean.
*/
public static boolean doesCreatureAttackAI(final Player ai, final Card card) {
AiAttackController aiAtk = new AiAttackController(ai);
Combat combat = new Combat(ai);
aiAtk.declareAttackers(combat);
return combat.isAttacking(card);
}
/**
* getMostExpensivePermanentAI.
*
* @param all
* the all
* @return the card
*/
public static Card getMostExpensivePermanentAI(final Collection<Card> all) {
Card biggest = null;
int bigCMC = -1;
for (final Card card : all) {
int curCMC = card.getCMC();
// Add all cost of all auras with the same controller
final List<Card> auras = CardLists.filterControlledBy(card.getEnchantedBy(), card.getController());
curCMC += Aggregates.sum(auras, CardPredicates.Accessors.fnGetCmc) + auras.size();
if (curCMC >= bigCMC) {
bigCMC = curCMC;
biggest = card;
}
}
return biggest;
}
/**
* <p>
* getMostProminentCardName.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link java.lang.String} object.
*/
public static String getMostProminentCardName(final List<Card> list) {
if (list.size() == 0) {
return "";
}
final Map<String, Integer> map = new HashMap<String, Integer>();
for (final Card c : list) {
final String name = c.getName();
Integer currentCnt = map.get(name);
map.put(name, currentCnt == null ? Integer.valueOf(1) : Integer.valueOf(1 + currentCnt));
} // for
int max = 0;
String maxName = "";
for (final Entry<String, Integer> entry : map.entrySet()) {
final String type = entry.getKey();
// Log.debug(type + " - " + entry.getValue());
if (max < entry.getValue()) {
max = entry.getValue();
maxName = type;
}
}
return maxName;
}
/**
* <p>
* getMostProminentCreatureType.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link java.lang.String} object.
*/
public static String getMostProminentCreatureType(final List<Card> list) {
if (list.size() == 0) {
return "";
}
final Map<String, Integer> map = new HashMap<String, Integer>();
for (final Card c : list) {
final ArrayList<String> typeList = c.getType();
for (final String var : typeList) {
if (CardType.isACreatureType(var)) {
if (!map.containsKey(var)) {
map.put(var, 1);
} else {
map.put(var, map.get(var) + 1);
}
}
}
} // for
int max = 0;
String maxType = "";
for (final Entry<String, Integer> entry : map.entrySet()) {
final String type = entry.getKey();
// Log.debug(type + " - " + entry.getValue());
if (max < entry.getValue()) {
max = entry.getValue();
maxType = type;
}
}
return maxType;
}
/**
* <p>
* getMostProminentColor.
* </p>
*
* @param list
* a {@link forge.CardList} object.
* @return a {@link java.lang.String} object.
*/
public static String getMostProminentColor(final List<Card> list) {
byte colors = CardFactoryUtil.getMostProminentColors(list);
for(byte c : MagicColor.WUBRG) {
if ( (colors & c) != 0 )
return MagicColor.toLongString(c);
}
return MagicColor.Constant.WHITE; // no difference, there was no prominent color
}
public static String getMostProminentColor(final List<Card> list, final List<String> restrictedToColors) {
byte colors = CardFactoryUtil.getMostProminentColorsFromList(list, restrictedToColors);
for (byte c : MagicColor.WUBRG) {
if ((colors & c) != 0) {
return MagicColor.toLongString(c);
}
}
return restrictedToColors.get(0); // no difference, there was no prominent color
}
public static List<String> getColorByProminence(final List<Card> list) {
int cntColors = MagicColor.WUBRG.length;
final List<Pair<Byte,Integer>> map = new ArrayList<Pair<Byte,Integer>>();
for(int i = 0; i < cntColors; i++) {
map.add(MutablePair.of(MagicColor.WUBRG[i], 0));
}
for (final Card crd : list) {
ColorSet color = CardUtil.getColors(crd);
if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue()+1));
if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue()+1));
if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue()+1));
if (color.hasRed()) map.get(3).setValue(Integer.valueOf(map.get(3).getValue()+1));
if (color.hasGreen()) map.get(4).setValue(Integer.valueOf(map.get(4).getValue()+1));
} // for
Collections.sort(map, new Comparator<Pair<Byte,Integer>>() {
@Override public int compare(Pair<Byte, Integer> o1, Pair<Byte, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
// will this part be once dropped?
List<String> result = new ArrayList<String>(cntColors);
for(Pair<Byte, Integer> idx : map) { // fetch color names in the same order
result.add(MagicColor.toLongString(idx.getKey()));
}
// reverse to get indices for most prominent colors first.
return result;
}
/**
* <p>
* getWorstLand.
* </p>
*
* @param lands
* a {@link forge.CardList} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstLand(final List<Card> lands) {
Card worstLand = null;
int maxScore = 0;
// first, check for tapped, basic lands
for (Card tmp : lands) {
int score = tmp.isTapped() ? 2 : 0;
score += tmp.isBasicLand() ? 1 : 0;
score += tmp.isCreature() ? 4 : 0;
if (score >= maxScore) {
worstLand = tmp;
maxScore = score;
}
}
return worstLand;
} // end getWorstLand
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
@Override
public boolean apply(Deck d) {
for(Entry<DeckSection, CardPool> cp: d) {
for(Entry<PaperCard, Integer> e : cp.getValue()) {
if ( e.getKey().getRules().getAiHints().getRemAIDecks() )
return false;
}
}
return true;
}
};
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
List<String> chosen = new ArrayList<String>();
Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Player opp = ai.getOpponent();
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if (logic.equals("MostProminentInHumanDeck")) {
chosen.add(ComputerUtilCard.getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices));
} else if (logic.equals("MostProminentInComputerDeck")) {
chosen.add(ComputerUtilCard.getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), ai), colorChoices));
} else if (logic.equals("MostProminentDualInComputerDeck")) {
List<String> prominence = ComputerUtilCard.getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
chosen.add(prominence.get(0));
chosen.add(prominence.get(1));
}
else if (logic.equals("MostProminentInGame")) {
chosen.add(ComputerUtilCard.getMostProminentColor(game.getCardsInGame(), colorChoices));
}
else if (logic.equals("MostProminentHumanCreatures")) {
List<Card> list = opp.getCreaturesInPlay();
if (list.isEmpty()) {
list = CardLists.filter(CardLists.filterControlledBy(game.getCardsInGame(), opp), CardPredicates.Presets.CREATURES);
}
chosen.add(ComputerUtilCard.getMostProminentColor(list, colorChoices));
}
else if (logic.equals("MostProminentComputerControls")) {
chosen.add(ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield), colorChoices));
}
else if (logic.equals("MostProminentHumanControls")) {
chosen.add(ComputerUtilCard.getMostProminentColor(ai.getOpponent().getCardsIn(ZoneType.Battlefield), colorChoices));
}
else if (logic.equals("MostProminentPermanent")) {
final List<Card> list = game.getCardsIn(ZoneType.Battlefield);
chosen.add(ComputerUtilCard.getMostProminentColor(list, colorChoices));
}
else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) {
chosen.add(ComputerUtilCard.getMostProminentColor(game.getCombat().getAttackers(), colorChoices));
}
else if (logic.equals("MostProminentInActivePlayerHand")) {
chosen.add(ComputerUtilCard.getMostProminentColor(game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Hand), colorChoices));
}
else if (logic.equals("MostProminentKeywordInComputerDeck")) {
List<Card> list = ai.getAllCards();
int m1 = 0;
String chosenColor = MagicColor.Constant.WHITE;
for (final String c : MagicColor.Constant.ONLY_COLORS) {
final int cmp = CardLists.filter(list, CardPredicates.containsKeyword(c)).size();
if (cmp > m1) {
m1 = cmp;
chosenColor = c;
}
}
chosen.add(chosenColor);
}
}
if (chosen.size() == 0) {
chosen.add(MagicColor.Constant.GREEN);
}
return chosen;
}
}

View File

@@ -1,30 +1,20 @@
package forge.game.ai;
package forge.ai;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.*;
import forge.game.player.Player;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import forge.Card;
import forge.CardLists;
import forge.CounterType;
import forge.card.ability.AbilityUtils;
import forge.card.cost.Cost;
import forge.card.cost.CostDamage;
import forge.card.cost.CostDiscard;
import forge.card.cost.CostPart;
import forge.card.cost.CostPayLife;
import forge.card.cost.CostPayment;
import forge.card.cost.CostPutCounter;
import forge.card.cost.CostRemoveCounter;
import forge.card.cost.CostSacrifice;
import forge.card.spellability.Spell;
import forge.card.spellability.SpellAbility;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.TextUtil;
/**
* TODO: Write javadoc for this type.
*
@@ -70,21 +60,11 @@ public class ComputerUtilCost {
if (cost == null) {
return true;
}
double p1p1Percent = .25;
if (source.isCreature()) {
p1p1Percent = .1;
}
final double otherPercent = .9;
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostRemoveCounter) {
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
// A card has a 25% chance per counter to be able to pass
// through here
// 4+ counters will always pass. 0 counters will never
final CounterType type = remCounter.getCounter();
final double percent = type.name().equals("P1P1") ? p1p1Percent : otherPercent;
final int currentNum = source.getCounters(type);
final CounterType type = remCounter.counter;
if (!part.payCostFromSource()) {
if (type.name().equals("P1P1")) {
return false;
@@ -96,15 +76,6 @@ public class ComputerUtilCost {
if (type.name().equals("P1P1") && source.getLethalDamage() <= 1) {
return false;
}
Integer amount = part.convertAmount();
if (amount == null) {
amount = currentNum;
}
final double chance = percent * (currentNum / amount);
if (chance <= MyRandom.getRandom().nextFloat()) {
return false;
}
}
}
return true;
@@ -307,7 +278,7 @@ public class ComputerUtilCost {
* </p>
*
* @param hostCard
* a {@link forge.Card} object.
* a {@link forge.game.card.Card} object.
* @param costString
* a {@link java.lang.String} object.
* @return a boolean.
@@ -335,7 +306,7 @@ public class ComputerUtilCost {
* </p>
*
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @return a boolean.
@@ -378,7 +349,7 @@ public class ComputerUtilCost {
} // canPayCost()
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, List<Player> payers) {
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
boolean payForOwnOnly = "OnlyOwn".equals(sa.getParam("UnlessAI"));
boolean payOwner = sa.hasParam("UnlessAI") ? sa.getParam("UnlessAI").startsWith("Defined") : false;
boolean payNever = "Never".equals(sa.getParam("UnlessAI"));
@@ -395,7 +366,8 @@ public class ComputerUtilCost {
}
} else if (shockland) {
if (payer.getLife() > 3 && payer.canPayLife(2)) {
final int landsize = payer.getLandsInPlay().size();
// If the new land size would equal the CMC of a card in AIs hand, play it untapped
final int landsize = payer.getLandsInPlay().size() + 1;
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
if (landsize == c.getCMC()) {
return true;
@@ -414,6 +386,10 @@ public class ComputerUtilCost {
if (payerCreatures > sourceCreatures + 1) {
return false;
}
} else if ("LifeLE2".equals(sa.getParam("UnlessAI"))) {
if (payer.getLife() < 3) {
return true;
}
}
// AI will only pay when it's not already payed and only opponents abilities

View File

@@ -0,0 +1,911 @@
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.cost.Cost;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayment;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* TODO: Write javadoc for this type.
*
*/
public class ComputerUtilMana {
private final static boolean DEBUG_MANA_PAYMENT = false;
public static boolean canPayManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
cost = new ManaCostBeingPaid(cost); //check copy of cost so it doesn't modify the exist cost being paid
return payManaCost(cost, sa, ai, true, 0, true);
}
public static boolean payManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
return payManaCost(cost, sa, ai, false, 0, true);
}
public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana) {
return payManaCost(sa, ai, true, extraMana, true);
}
// Does not check if mana sources can be used right now, just checks for potential chance.
public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Player ai) {
sa.setActivatingPlayer(ai);
return payManaCost(sa, ai, true, 0, false);
}
public static boolean payManaCost(final Player ai, final SpellAbility sa) {
return payManaCost(sa, ai, false, 0, true);
}
private static void refundMana(List<Mana> manaSpent, Player ai, SpellAbility sa) {
if (sa.getHostCard() != null) {
sa.getHostCard().setCanCounter(true);
}
for (final Mana m : manaSpent) {
ai.getManaPool().addMana(m);
}
manaSpent.clear();
}
private static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, test, extraMana);
return payManaCost(cost, sa, ai, test, extraMana, checkPlayable);
}
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) {
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
final ManaPool manapool = ai.getManaPool();
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
if (part != ManaCostShard.X) {
if (cost.isPaid()) {
continue;
}
// get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction());
if (mana == null) {
continue; // no matching mana in the pool
}
else {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana)) {
manaSpentToPay.add(0, mana);
}
}
}
}
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
if(test)
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, test, cost.isPaid());
return true;
}
// arrange all mana abilities by color produced.
final Multimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, checkPlayable);
if (manaAbilityMap.isEmpty()) {
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, test, cost.isPaid());
return false;
}
if (DEBUG_MANA_PAYMENT) {
System.out.println("DEBUG_MANA_PAYMENT: manaAbilityMap = " + manaAbilityMap);
}
// select which abilities may be used for each shard
Multimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
// if (DEBUG_MANA_PAYMENT) {
// System.out.println((test ? "test -- " : "PROD -- ") + FThreads.debugGetStackTraceItem(5, true));
// for (Entry<ManaCostShard, Collection<SpellAbility>> src : sourcesForShards.entrySet()) {
// System.out.println("\t" +src.getKey() + " : " + src.getValue().size() + " source(s)");
// for (SpellAbility sss : src.getValue()) {
// System.out.printf("\t\t%s - %s%n", sss.getHostCard(), sss);
// }
// }
// }
List<String> paymentPlan = new ArrayList<String>();
ManaCostShard toPay = null;
// Loop over mana needed
while (!cost.isPaid()) {
toPay = getNextShardToPay(cost, sourcesForShards);
Collection<SpellAbility> saList = sourcesForShards.get(toPay);
SpellAbility saPayment = null;
if (saList != null) {
for (final SpellAbility ma : saList) {
if (ma.getHostCard() == sa.getHostCard()) {
continue;
}
final String typeRes = cost.getSourceRestriction();
if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().isType(typeRes)) {
continue;
}
if (canPayShardWithSpellAbility(toPay, ai, ma, sa, checkPlayable || !test)) {
saPayment = ma;
break;
}
}
} else {
break;
}
if (DEBUG_MANA_PAYMENT) {
paymentPlan.add(String.format("%s : (%s) %s", toPay, saPayment == null ? "LIFE" : saPayment.getHostCard(), saPayment));
}
if (saPayment == null) {
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
break; // cannot pay
}
cost.payPhyrexian();
if (!test) {
ai.payLife(2, sa.getHostCard());
}
continue;
}
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
if (test) {
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
//System.out.println(manaProduced);
payMultipleMana(cost, manaProduced, ai);
// remove from available lists
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
while (itSa.hasNext()) {
SpellAbility srcSa = itSa.next();
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
itSa.remove();
}
}
}
else {
if (saPayment.getPayCosts() != null) {
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
saList.remove(saPayment);
continue;
}
}
else {
System.err.println("Ability " + saPayment + " from " + saPayment.getHostCard() + " had NULL as payCost");
saPayment.getHostCard().tap();
}
ai.getGame().getStack().addAndUnfreeze(saPayment);
// subtract mana from mana pool
manapool.payManaFromAbility(sa, cost, saPayment);
// no need to remove abilities from resource map,
// once their costs are paid and consume resources, they can not be used again
}
}
handleOfferingsAI(sa, test, cost.isPaid());
// if (DEBUG_MANA_PAYMENT) {
// System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n",
// FThreads.debugGetCurrThreadId(), test ? "test" : "PROD", cost.isPaid() ? "*PAID*" : "failed", originalCost,
// extraMana, sa.getHostCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t"));
// }
if (!cost.isPaid()) {
refundMana(manaSpentToPay, ai, sa);
if (test) {
return false;
}
else {
System.out.println("ComputerUtil : payManaCost() cost was not paid for " + sa.getHostCard().getName() + ". Didn't find what to pay for " + toPay);
return false;
}
}
if (test)
refundMana(manaSpentToPay, ai, sa);
sa.getHostCard().setColorsPaid(cost.getColorsPaid());
// if (sa instanceof Spell_Permanent) // should probably add this
sa.getHostCard().setSunburstValue(cost.getSunburst());
return true;
} // payManaCost()
/**
* <p>
* getManaFrom.
* </p>
*
* @param saBeingPaidFor
* a {@link forge.game.spellability.SpellAbility} object.
* @return a {@link forge.game.mana.Mana} object.
*/
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) {
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction);
// Exclude border case
if (weightedOptions.isEmpty()) {
return null; // There is no matching mana in the pool
}
// select equal weight possibilities
List<Mana> manaChoices = new ArrayList<Mana>();
int bestWeight = Integer.MIN_VALUE;
for (Pair<Mana, Integer> option : weightedOptions) {
int thisWeight = option.getRight();
Mana thisMana = option.getLeft();
if (thisWeight > bestWeight) {
manaChoices.clear();
bestWeight = thisWeight;
}
if (thisWeight == bestWeight) {
// add only distinct Mana-s
boolean haveDuplicate = false;
for (Mana m : manaChoices) {
if (m.equals(thisMana)) {
haveDuplicate = true;
break;
}
}
if (!haveDuplicate) {
manaChoices.add(thisMana);
}
}
}
// got an only one best option?
if (manaChoices.size() == 1) {
return manaChoices.get(0);
}
// Let them choose then
return ai.getController().chooseManaFromPool(manaChoices);
}
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) {
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<Pair<Mana, Integer>>();
for (final Mana thisMana : manapool) {
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
continue;
}
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsManaRestrictions(saBeingPaidFor)) {
continue;
}
boolean canPay = manapool.canPayForShardWithColor(shard, thisMana.getColor());
if (!canPay || (shard.isSnow() && !thisMana.isSnow())) {
continue;
}
if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().isType(restriction)) {
continue;
}
// prefer colorless mana to spend
int weight = thisMana.isColorless() ? 5 : 0;
// prefer restricted mana to spend
if (thisMana.isRestricted()) {
weight += 2;
}
// Spend non-snow mana first
if (!thisMana.isSnow()) {
weight += 1;
}
weightedOptions.add(Pair.of(thisMana, weight));
}
return weightedOptions;
}
private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost,
ManaCostShard toPay, SpellAbility saPayment) {
AbilityManaPart m = saPayment.getManaPart();
if (m.isComboMana())
getComboManaChoice(ai, saPayment, sa, cost);
else if (saPayment.getApi() == ApiType.ManaReflected) {
System.out.println("Evaluate reflected mana of: " + saPayment.getHostCard());
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
for (byte c : MagicColor.WUBRG) {
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
m.setExpressChoice(MagicColor.toShortString(c));
return;
}
}
} else if (m.isAnyMana()) {
byte colorChoice = 0;
if (toPay.isOr2Colorless())
colorChoice = toPay.getColorMask();
else {
for (byte c : MagicColor.WUBRG) {
if (ai.getManaPool().canPayForShardWithColor(toPay, c)) {
colorChoice = c;
break;
}
}
}
m.setExpressChoice(MagicColor.toShortString(colorChoice));
}
}
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
final Card sourceCard = ma.getHostCard();
if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
AbilityManaPart m = ma.getManaPart();
if (!m.meetsManaRestrictions(sa)) {
return false;
}
if (checkCosts) {
// Check if AI can still play this mana ability
ma.setActivatingPlayer(ai);
if (ma.getPayCosts() != null) { // if the AI can't pay the additional costs skip the mana ability
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
return false;
}
} else if (sourceCard.isTapped()) {
return false;
}
}
if (m.isComboMana()) {
for (String s : m.getComboColors().split(" ")) {
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, MagicColor.fromName(s)))
return true;
}
return false;
} else if (ma.getApi() == ApiType.ManaReflected) {
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
for (byte c : MagicColor.WUBRG) {
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
m.setExpressChoice(MagicColor.toShortString(c));
return true;
}
}
return false;
}
return true;
}
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost, Multimap<ManaCostShard, SpellAbility> sourcesForShards) {
// mind the priorities
// * Pay mono-colored first,
// * Pay 2/C with matching colors
// * pay hybrids
// * pay phyrexian, keep mana for colorless
// * pay colorless
for (ManaCostShard s : cost.getDistinctShards()) { // should check in which order EnumMap enumerates keys. If it's same as enum member declaration, nothing else needs to be done.
return s;
}
return null;
}
private static void adjustManaCostToAvoidNegEffects(ManaCostBeingPaid cost, final Card card, Player ai) {
// Make mana needed to avoid negative effect a mandatory cost for the AI
for (String manaPart : card.getSVar("ManaNeededToAvoidNegativeEffect").split(",")) {
// convert long color strings to short color strings
byte mask = MagicColor.fromName(manaPart);
// make mana mandatory for AI
if (!cost.needsColor(mask, ai.getManaPool()) && cost.getColorlessManaAmount() > 0) {
ManaCostShard shard = ManaCostShard.valueOf(mask);
cost.increaseShard(shard, 1);
cost.decreaseColorlessMana(1);
}
}
}
/**
* <p>
* getComboManaChoice.
* </p>
*
* @param abMana
* a {@link forge.card.spellability.AbilityMana} object.
* @param saRoot
* a {@link forge.game.spellability.SpellAbility} object.
* @param cost
* a {@link forge.game.mana.ManaCostBeingPaid} object.
* @return String
*/
private static void getComboManaChoice(final Player ai, final SpellAbility manaAb, final SpellAbility saRoot, final ManaCostBeingPaid cost) {
final StringBuilder choiceString = new StringBuilder();
final Card source = manaAb.getHostCard();
final AbilityManaPart abMana = manaAb.getManaPart();
if (abMana.isComboMana()) {
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), saRoot) : 1;
final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost);
final String[] comboColors = abMana.getComboColors().split(" ");
for (int nMana = 1; nMana <= amount; nMana++) {
String choice = "";
// Use expressChoice first
if (!abMana.getExpressChoice().isEmpty()) {
choice = abMana.getExpressChoice();
abMana.clearExpressChoice();
byte colorMask = MagicColor.fromName(choice);
if (abMana.canProduce(choice, manaAb) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) {
choiceString.append(choice);
payMultipleMana(testCost, choice, ai);
continue;
}
}
// check colors needed for cost
if (!testCost.isPaid()) {
// Loop over combo colors
for (String color : comboColors) {
if (testCost.isAnyPartPayableWith(MagicColor.fromName(color), ai.getManaPool())) {
payMultipleMana(testCost, color, ai);
if (nMana != 1) {
choiceString.append(" ");
}
choiceString.append(color);
choice = color;
break;
}
}
if (!choice.isEmpty()) {
continue;
}
}
// check if combo mana can produce most common color in hand
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(
ZoneType.Hand));
if (!commonColor.isEmpty() && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
choice = MagicColor.toShortString(commonColor);
}
else {
// default to first color
choice = comboColors[0];
}
if (nMana != 1) {
choiceString.append(" ");
}
choiceString.append(choice);
}
}
if (choiceString.toString().isEmpty()) {
choiceString.append("0");
}
abMana.setExpressChoice(choiceString.toString());
}
/**
* <p>
* payMultipleMana.
* </p>
* @param testCost
*
* @param mana
* a {@link java.lang.String} object.
* @return a boolean.
*/
private final static String payMultipleMana(ManaCostBeingPaid testCost, String mana, final Player p) {
List<String> unused = new ArrayList<String>(4);
for (String manaPart : TextUtil.split(mana, ' ')) {
if (StringUtils.isNumeric(manaPart)) {
for (int i = Integer.parseInt(manaPart); i > 0; i--) {
boolean wasNeeded = testCost.ai_payMana("1", p.getManaPool());
if (!wasNeeded) {
unused.add(Integer.toString(i));
break;
}
}
}
else {
String color = MagicColor.toShortString(manaPart);
boolean wasNeeded = testCost.ai_payMana(color, p.getManaPool());
if (!wasNeeded) {
unused.add(color);
}
}
}
return unused.isEmpty() ? null : StringUtils.join(unused, ' ');
}
/**
* Find all mana sources.
* @param manaAbilityMap
* @param partSources
* @param partPriority
* @param costParts
* @param foundAllSources
* @return Were all mana sources found?
*/
private static Multimap<ManaCostShard, SpellAbility> groupAndOrderToPayShards(final Player ai, final Multimap<Integer, SpellAbility> manaAbilityMap,
final ManaCostBeingPaid cost) {
Multimap<ManaCostShard, SpellAbility> res = ArrayListMultimap.create();
// loop over cost parts
for (ManaCostShard shard : cost.getDistinctShards()) {
if (shard == ManaCostShard.S) {
res.putAll(shard, manaAbilityMap.get(ManaAtom.IS_SNOW));
continue;
}
if (shard.isOr2Colorless()) {
Integer colorKey = Integer.valueOf(shard.getColorMask());
if (manaAbilityMap.containsKey(colorKey))
res.putAll(shard, manaAbilityMap.get(colorKey));
if (manaAbilityMap.containsKey(ManaAtom.COLORLESS))
res.putAll(shard, manaAbilityMap.get(ManaAtom.COLORLESS));
continue;
}
for (Entry<Integer, SpellAbility> kv : manaAbilityMap.entries()) {
// apply mana color change matrix here
if (ai.getManaPool().canPayForShardWithColor(shard, kv.getKey().byteValue())) {
res.put(shard, kv.getValue());
}
}
}
if (cost.getColorlessManaAmount() > 0 && manaAbilityMap.containsKey(ManaAtom.COLORLESS)) {
res.putAll(ManaCostShard.COLORLESS, manaAbilityMap.get(ManaAtom.COLORLESS));
}
return res;
}
/**
* Calculate the ManaCost for the given SpellAbility.
* @param sa
* @param test
* @param extraMana
* @return ManaCost
*/
static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
ZoneType castFromBackup = null;
if (test && sa.isSpell()) {
castFromBackup = sa.getHostCard().getCastFrom();
sa.getHostCard().setCastFrom(sa.getHostCard().getZone().getZoneType());
}
Cost payCosts = sa.getPayCosts();
CostPartMana manapart = payCosts != null ? payCosts.getCostMana() : null;
final ManaCost mana = payCosts != null ? ( manapart == null ? ManaCost.ZERO : manapart.getManaCostFor(sa) ) : ManaCost.NO_COST;
String restriction = null;
if (payCosts != null && payCosts.getCostMana() != null) {
restriction = payCosts.getCostMana().getRestiction();
}
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
cost.applySpellCostChange(sa, test);
final Card card = sa.getHostCard();
// Tack xMana Payments into mana here if X is a set value
if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0 || extraMana > 0)) {
int manaToAdd = 0;
if (test && extraMana > 0) {
final int multiplicator = Math.max(cost.getXcounter(), 1);
manaToAdd = extraMana * multiplicator;
} else {
// For Count$xPaid set PayX in the AFs then use that here
// Else calculate it as appropriate.
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
if (!card.getSVar(xSvar).equals("")) {
if (xSvar.equals("PayX")) {
manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
} else {
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
}
}
}
String manaXColor = sa.getParam("XColor");
ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor);
cost.increaseShard(shardToGrow, manaToAdd);
if (!test) {
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
}
}
if (test && sa.isSpell()) {
sa.getHostCard().setCastFrom(castFromBackup);
}
return cost;
}
//This method is currently used by AI to estimate available mana
public static List<Card> getAvailableMana(final Player ai, final boolean checkPlayable) {
final List<Card> list = ai.getCardsIn(ZoneType.Battlefield);
list.addAll(ai.getCardsIn(ZoneType.Hand));
final List<Card> manaSources = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (final SpellAbility am : getAIPlayableMana(c)) {
am.setActivatingPlayer(ai);
if (!checkPlayable || am.canPlay()) {
return true;
}
}
return false;
}
}); // CardListFilter
final List<Card> sortedManaSources = new ArrayList<Card>();
final List<Card> otherManaSources = new ArrayList<Card>();
final List<Card> colorlessManaSources = new ArrayList<Card>();
final List<Card> oneManaSources = new ArrayList<Card>();
final List<Card> twoManaSources = new ArrayList<Card>();
final List<Card> threeManaSources = new ArrayList<Card>();
final List<Card> fourManaSources = new ArrayList<Card>();
final List<Card> fiveManaSources = new ArrayList<Card>();
final List<Card> anyColorManaSources = new ArrayList<Card>();
// Sort mana sources
// 1. Use lands that can only produce colorless mana without
// drawback/cost first
// 2. Search for mana sources that have a certain number of abilities
// 3. Use lands that produce any color many
// 4. all other sources (creature, costs, drawback, etc.)
for (Card card : manaSources) {
if (card.isCreature() || card.isEnchanted()) {
otherManaSources.add(card);
continue; // don't use creatures before other permanents
}
int usableManaAbilities = 0;
boolean needsLimitedResources = false;
boolean producesAnyColor = false;
final ArrayList<SpellAbility> manaAbilities = getAIPlayableMana(card);
for (final SpellAbility m : manaAbilities) {
if (m.getManaPart().isAnyMana()) {
producesAnyColor = true;
}
final Cost cost = m.getPayCosts();
if (cost != null) {
needsLimitedResources |= !cost.isReusuableResource();
}
// if the AI can't pay the additional costs skip the mana ability
if (cost != null) {
m.setActivatingPlayer(ai);
if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m)) {
continue;
}
}
// don't use abilities with dangerous drawbacks
AbilitySub sub = m.getSubAbility();
if (sub != null && !card.getName().equals("Pristine Talisman") && !card.getName().equals("Zhur-Taa Druid")) {
if (!SpellApiToAi.Converter.get(sub.getApi()).chkDrawbackWithSubs(ai, sub)) {
continue;
}
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
}
usableManaAbilities++;
}
if (needsLimitedResources) {
otherManaSources.add(card);
} else if (producesAnyColor) {
anyColorManaSources.add(card);
} else if (usableManaAbilities == 1) {
if (manaAbilities.get(0).getManaPart().mana().equals("1")) {
colorlessManaSources.add(card);
} else {
oneManaSources.add(card);
}
} else if (usableManaAbilities == 2) {
twoManaSources.add(card);
} else if (usableManaAbilities == 3) {
threeManaSources.add(card);
} else if (usableManaAbilities == 4) {
fourManaSources.add(card);
} else {
fiveManaSources.add(card);
}
}
sortedManaSources.addAll(colorlessManaSources);
sortedManaSources.addAll(oneManaSources);
sortedManaSources.addAll(twoManaSources);
sortedManaSources.addAll(threeManaSources);
sortedManaSources.addAll(fourManaSources);
sortedManaSources.addAll(fiveManaSources);
sortedManaSources.addAll(anyColorManaSources);
//use better creatures later
ComputerUtilCard.sortByEvaluateCreature(otherManaSources);
Collections.reverse(otherManaSources);
sortedManaSources.addAll(otherManaSources);
return sortedManaSources;
} // getAvailableMana()
//This method is currently used by AI to estimate mana available
private static Multimap<Integer, SpellAbility> groupSourcesByManaColor(final Player ai, boolean checkPlayable) {
final Multimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
final Game game = ai.getGame();
// Loop over all current available mana sources
for (final Card sourceCard : getAvailableMana(ai, checkPlayable)) {
for (final SpellAbility m : getAIPlayableMana(sourceCard)) {
m.setActivatingPlayer(ai);
if (checkPlayable && !m.canPlay()) {
continue;
}
// don't use abilities with dangerous drawbacks
AbilitySub sub = m.getSubAbility();
if (sub != null) {
if (!SpellApiToAi.Converter.get(sub.getApi()).chkDrawbackWithSubs(ai, sub)) {
continue;
}
}
manaMap.put(ManaAtom.COLORLESS, m); // add to colorless source list
AbilityManaPart mp = m.getManaPart();
// setup produce mana replacement effects
final Map<String, Object> repParams = new HashMap<String, Object>();
repParams.put("Event", "ProduceMana");
repParams.put("Mana", mp.getOrigProduced());
repParams.put("Affected", sourceCard);
repParams.put("Player", ai);
repParams.put("AbilityMana", m);
for (final Player p : game.getPlayers()) {
for (final Card crd : p.getAllCards()) {
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
if (replacementEffect.requirementsCheck(game)
&& replacementEffect.canReplace(repParams)
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
String repType = crd.getSVar(replacementEffect.getMapParams().get("ManaReplacement"));
if (repType.contains("Chosen")) {
repType = repType.replace("Chosen", MagicColor.toShortString(crd.getChosenColor().get(0)));
}
mp.setManaReplaceType(repType);
}
}
}
}
Set<String> reflectedColors = CardUtil.getReflectableManaColors(m);
// find possible colors
if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) {
manaMap.put(ManaAtom.WHITE, m);
}
if (mp.canProduce("U", m) || reflectedColors.contains(MagicColor.Constant.BLUE)) {
manaMap.put(ManaAtom.BLUE, m);
}
if (mp.canProduce("B", m) || reflectedColors.contains(MagicColor.Constant.BLACK)) {
manaMap.put(ManaAtom.BLACK, m);
}
if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) {
manaMap.put(ManaAtom.RED, m);
}
if (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) {
manaMap.put(ManaAtom.GREEN, m);
}
if (mp.isSnow()) {
manaMap.put(ManaAtom.IS_SNOW, m);
}
} // end of mana abilities loop
} // end of mana sources loop
return manaMap;
}
/**
* <p>
* determineLeftoverMana.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @return a int.
* @since 1.0.15
*/
public static int determineLeftoverMana(final SpellAbility sa, final Player player) {
for (int i = 1; i < 100; i++)
if (!canPayManaCost(sa, player, i))
return i - 1;
return 99;
}
// Returns basic mana abilities plus "reflected mana" abilities
/**
* <p>
* getAIPlayableMana.
* </p>
*
* @return a {@link java.util.ArrayList} object.
*/
public static final ArrayList<SpellAbility> getAIPlayableMana(Card c) {
final ArrayList<SpellAbility> res = new ArrayList<SpellAbility>();
for (final SpellAbility a : c.getManaAbility()) {
// if a mana ability has a mana cost the AI will miscalculate
// if there is a parent ability the AI can't use it
final Cost cost = a.getPayCosts();
if (!cost.hasNoManaCost() || (a.getApi() != ApiType.Mana && a.getApi() != ApiType.ManaReflected)) {
continue;
}
if (!res.contains(a)) {
res.add(a);
}
}
return res;
}
private static void handleOfferingsAI(final SpellAbility sa, boolean test, boolean costIsPaid) {
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getController().getGame().getAction().sacrifice(offering, sa);
}
sa.resetSacrificedAsOffering();
}
}
}

View File

@@ -0,0 +1,63 @@
package forge.ai;
import forge.game.Game;
import forge.game.player.LobbyPlayer;
import forge.game.player.Player;
public class LobbyPlayerAi extends LobbyPlayer {
public LobbyPlayerAi(String name) {
super(name);
}
private String aiProfile = "";
private boolean rotateProfileEachGame;
private boolean allowCheatShuffle;
public boolean isAllowCheatShuffle() {
return allowCheatShuffle;
}
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
this.allowCheatShuffle = allowCheatShuffle;
}
public void setAiProfile(String profileName) {
aiProfile = profileName;
}
public String getAiProfile() {
return aiProfile;
}
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
this.rotateProfileEachGame = rotateProfileEachGame;
}
@Override
protected PlayerType getType() {
return PlayerType.COMPUTER;
}
@Override
public PlayerControllerAi createControllerFor(Player ai) {
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
result.allowCheatShuffle(allowCheatShuffle);
return result;
}
@Override
public Player createIngamePlayer(Game game) {
Player ai = new Player(getName(), game);
ai.setFirstController(createControllerFor(ai));
if( rotateProfileEachGame ) {
setAiProfile(AiProfileUtil.getRandomProfile());
System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));
}
return ai;
}
@Override
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
}

View File

@@ -0,0 +1,765 @@
package forge.ai;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.CharmAi;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.deck.Deck;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GameType;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.LobbyPlayer;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerController;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.*;
import forge.game.trigger.Trigger;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.awt.event.MouseEvent;
import java.security.InvalidParameterException;
import java.util.*;
/**
* A prototype for player controller class
*
* Handles phase skips for now.
*/
public class PlayerControllerAi extends PlayerController {
private final AiController brains;
public PlayerControllerAi(Game game, Player p, LobbyPlayer lp) {
super(game, p, lp);
brains = new AiController(p, game);
}
public void allowCheatShuffle(boolean value){
brains.allowCheatShuffle(value);
}
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, MouseEvent triggerEvent) {
if (abilities.size() == 0) {
return null;
}
else {
return abilities.get(0);
}
}
/**
* TODO: Write javadoc for this method.
* @param c
*/
/**public void playFromSuspend(Card c) {
final List<SpellAbility> choices = c.getBasicSpells();
c.setSuspendCast(true);
getAi().chooseAndPlaySa(choices, true, true);
}**/
/**
* TODO: Write javadoc for this method.
* @return
*/
public AiController getAi() {
return brains;
}
@Override
public List<PaperCard> sideboard(Deck deck, GameType gameType) {
// AI does not know how to sideboard
return null;
}
@Override
public Map<Card, Integer> assignCombatDamage(Card attacker, List<Card> blockers, int damageDealt, GameEntity defender, boolean overrideOrder) {
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
}
@Override
public Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero) {
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
return null; // return incorrect value to indicate that
}
@Override
public List<Card> choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List<Card> validTargets, String message) {
return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, max, sa, false, min == 0);
}
@Override
public List<Card> choosePermanentsToDestroy(SpellAbility sa, int min, int max, List<Card> validTargets, String message) {
return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, max, sa, true, min == 0);
}
@Override
public List<Card> chooseCardsForEffect(List<Card> sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) {
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional);
}
@Override
public <T extends GameEntity> T chooseSingleEntityForEffect(Collection<T> options, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) {
ApiType api = sa.getApi();
if (null == api) {
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
}
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, options, isOptional, targetedPlayer);
}
@Override
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title) {
ApiType api = sa.getApi();
if (null == api) {
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
}
return SpellApiToAi.Converter.get(api).chooseSingleSpellAbility(player, sa, spells);
}
@Override
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return getAi().confirmAction(sa, mode, message);
}
@Override
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
return getAi().confirmStaticApplication(hostCard, affected, logic, message);
}
@Override
public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map<String, String> triggerParams, boolean isMandatory) {
if (triggerParams.containsKey("DelayedTrigger")) {
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
// needs to be expanded when a more difficult cards comes up
return true;
}
// Store/replace target choices more properly to get this SA cleared.
TargetChoices tc = null;
TargetChoices subtc = null;
boolean storeChoices = sa.getTargetRestrictions() != null;
final SpellAbility sub = sa.getSubAbility();
boolean storeSubChoices = sub != null && sub.getTargetRestrictions() != null;
boolean ret = true;
if (storeChoices) {
tc = sa.getTargets();
sa.resetTargets();
}
if (storeSubChoices) {
subtc = sub.getTargets();
sub.resetTargets();
}
// There is no way this doTrigger here will have the same target as stored above
// So it's possible it's making a different decision here than will actually happen
if (!brains.doTrigger(sa, isMandatory)) {
ret = false;
}
if (storeChoices) {
sa.resetTargets();
sa.setTargets(tc);
}
if (storeSubChoices) {
sub.resetTargets();
sub.setTargets(subtc);
}
return ret;
}
@Override
public boolean getWillPlayOnFirstTurn(boolean isFirstGame) {
return true; // AI is brave :)
}
@Override
public List<Card> orderBlockers(Card attacker, List<Card> blockers) {
return AiBlockController.orderBlockers(attacker, blockers);
}
@Override
public List<Card> orderAttackers(Card blocker, List<Card> attackers) {
return AiBlockController.orderAttackers(blocker, attackers);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#reveal(java.lang.String, java.util.List)
*/
@Override
public void reveal(Collection<Card> cards, ZoneType zone, Player owner, String messagePrefix) {
// We don't know how to reveal cards to AI
}
@Override
public ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN) {
List<Card> toBottom = new ArrayList<Card>();
List<Card> toTop = new ArrayList<Card>();
for (Card c: topN) {
if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c)) {
toBottom.add(c);
}
else {
toTop.add(c);
}
}
// put the rest on top in random order
Collections.shuffle(toTop);
return ImmutablePair.of(toTop, toBottom);
}
@Override
public boolean willPutCardOnTop(Card c) {
return true; // AI does not know what will happen next (another clash or that would become his topdeck)
}
@Override
public List<Card> orderMoveToZoneList(List<Card> cards, ZoneType destinationZone) {
//TODO Add logic for AI ordering here
return cards;
}
@Override
public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> validCards, int min, int max) {
if (p == player) {
return brains.getCardsToDiscard(min, max, validCards, sa);
}
boolean isTargetFriendly = !p.isOpponentOf(player);
return isTargetFriendly
? ComputerUtil.getCardsToDiscardFromFriend(player, p, sa, validCards, min, max)
: ComputerUtil.getCardsToDiscardFromOpponent(player, p, sa, validCards, min, max);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#mayPlaySpellAbilityForFree(forge.card.spellability.SpellAbility)
*/
@Override
public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChooseNewTargets) {
// Ai is known to set targets in doTrigger, so if it cannot choose new targets, we won't call canPlays
if (mayChooseNewTargets) {
if (copySA instanceof Spell) {
Spell spell = (Spell) copySA;
((PlayerControllerAi) player.getController()).getAi().canPlayFromEffectAI(spell, true, true);
}
else {
getAi().canPlaySa(copySA);
}
}
ComputerUtil.playSpellAbilityForFree(player, copySA);
}
@Override
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
if (canSetupTargets)
brains.doTrigger(effectSA, true); // first parameter does not matter, since return value won't be used
ComputerUtil.playNoStack(player, effectSA, game);
}
@Override
public void playMiracle(SpellAbility miracle, Card card) {
getAi().chooseAndPlaySa(false, false, miracle);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseCardsToDelve(int, java.util.List)
*/
@Override
public List<Card> chooseCardsToDelve(int colorlessCost, List<Card> grave) {
return getAi().chooseCardsToDelve(colorlessCost, grave);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseTargets(forge.card.spellability.SpellAbility, forge.card.spellability.SpellAbilityStackInstance)
*/
@Override
public TargetChoices chooseNewTargetsFor(SpellAbility ability) {
// AI currently can't do this. But when it can it will need to be based on Ability API
return null;
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseCardsToDiscardUnlessType(int, java.util.List, java.lang.String, forge.card.spellability.SpellAbility)
*/
@Override
public List<Card> chooseCardsToDiscardUnlessType(int num, List<Card> hand, String uType, SpellAbility sa) {
final List<Card> cardsOfType = CardLists.getType(hand, uType);
if (!cardsOfType.isEmpty()) {
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
return Lists.newArrayList(toDiscard);
}
return getAi().getCardsToDiscard(num, (String[])null, sa);
}
@Override
public Mana chooseManaFromPool(List<Mana> manaChoices) {
return manaChoices.get(0); // no brains used
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#ChooseSomeType(java.lang.String, java.util.List, java.util.List)
*/
@Override
public String chooseSomeType(String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes, boolean isOptional) {
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), invalidTypes);
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty())
{
chosen = validTypes.get(0);
Log.warn("AI has no idea how to choose " + kindOfType +", defaulting to 1st element: chosen");
}
game.getAction().nofityOfValue(sa, null, "Computer picked: " + chosen, player);
return chosen;
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#confirmReplacementEffect(forge.card.replacement.ReplacementEffect, forge.card.spellability.SpellAbility, java.lang.String)
*/
@Override
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
return brains.aiShouldRun(replacementEffect, effectSA);
}
@Override
public List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer) {
if (!ComputerUtil.wantMulligan(player)) {
return null;
}
if (!isCommander) {
return player.getCardsIn(ZoneType.Hand);
}
else {
return ComputerUtil.getPartialParisCandidates(player);
}
}
@Override
public void declareAttackers(Player attacker, Combat combat) {
brains.declareAttackers(attacker, combat);
}
@Override
public void declareBlockers(Player defender, Combat combat) {
brains.declareBlockersFor(defender, combat);
}
@Override
public SpellAbility chooseSpellAbilityToPlay() {
return brains.choooseSpellAbilityToPlay();
}
@Override
public void playChosenSpellAbility(SpellAbility sa)
{
// System.out.println("Playing sa: " + sa);
if ( sa == Ability.PLAY_LAND_SURROGATE )
player.playLand(sa.getHostCard(), false);
else
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
}
@Override
public List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
return brains.getCardsToDiscard(numDiscard, (String[])null, null);
}
@Override
public List<Card> chooseCardsToRevealFromHand(int min, int max, List<Card> valid) {
int numCardsToReveal = Math.min(max, valid.size());
return numCardsToReveal == 0 ? Lists.<Card>newArrayList() : valid.subList(0, numCardsToReveal);
}
@Override
public boolean payManaOptional(Card c, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) {
final Ability ability = new AbilityStatic(c, cost, null) { @Override public void resolve() {} };
ability.setActivatingPlayer(c.getController());
if (ComputerUtilCost.canPayCost(ability, c.getController())) {
ComputerUtil.playNoStack(c.getController(), ability, game);
return true;
}
return false;
}
@Override
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
return brains.chooseSaToActivateFromOpeningHand(usableFromOpeningHand);
}
@Override
public int chooseNumber(SpellAbility sa, String title, int min, int max) {
return brains.chooseNumber(sa, title, min, max);
}
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
return brains.chooseNumber(sa, title, options, relatedPlayer);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseFlipResult(forge.Card, forge.game.player.Player, java.lang.String[], boolean)
*/
@Override
public boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call) {
if (call) {
// Win if possible
boolean result = false;
for (boolean s : results) {
if (s) {
result = s;
break;
}
}
return result;
} else {
// heads or tails, AI doesn't know which is better now
int i = MyRandom.getRandom().nextInt(results.length);
return results[i];
}
}
@Override
public Pair<SpellAbilityStackInstance, GameObject> chooseTarget(SpellAbility saSrc, List<Pair<SpellAbilityStackInstance, GameObject>> allTargets) {
// TODO Teach AI how to use Spellskite
return allTargets.get(0);
}
@Override
public void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value) {
// AI should take into consideration creature types, numbers and other information (mostly choices) arriving through this channel
}
@Override
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultVal) {
switch(kindOfChoice) {
case TapOrUntap: return true;
case UntapOrLeaveTapped: return defaultVal != null && defaultVal.booleanValue();
case UntapTimeVault: return false; // TODO Should AI skip his turn for time vault?
default:
return MyRandom.getRandom().nextBoolean();
}
}
@Override
public Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap) {
int i = MyRandom.getRandom().nextInt(options.size());
return choiceMap.get(options.get(i));
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseModeForAbility(forge.card.spellability.SpellAbility, java.util.List, int, int)
*/
@Override
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num) {
return CharmAi.chooseOptionsAi(sa, player, sa.isTrigger(), num, min, !player.equals(sa.getActivatingPlayer()));
}
@Override
public Pair<CounterType,String> chooseAndRemoveOrPutCounter(Card cardWithCounter) {
if (!cardWithCounter.hasCounters()) {
System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier");
return null;
}
final Player controller = cardWithCounter.getController();
final List<Player> enemies = player.getOpponents();
final List<Player> allies = player.getAllies();
allies.add(player);
List<CounterType> countersToIncrease = new ArrayList<CounterType>();
List<CounterType> countersToDecrease = new ArrayList<CounterType>();
for (final CounterType counter : cardWithCounter.getCounters().keySet()) {
if ((!ComputerUtil.isNegativeCounter(counter, cardWithCounter) && allies.contains(controller))
|| (ComputerUtil.isNegativeCounter(counter, cardWithCounter) && enemies.contains(controller))) {
countersToIncrease.add(counter);
} else {
countersToDecrease.add(counter);
}
}
if (!countersToIncrease.isEmpty()) {
int random = MyRandom.getRandom().nextInt(countersToIncrease.size());
return new ImmutablePair<CounterType,String>(countersToIncrease.get(random),"Put");
}
else if (!countersToDecrease.isEmpty()) {
int random = MyRandom.getRandom().nextInt(countersToDecrease.size());
return new ImmutablePair<CounterType,String>(countersToDecrease.get(random),"Remove");
}
// shouldn't reach here but just in case, remove random counter
List<CounterType> countersOnCard = new ArrayList<CounterType>();
int random = MyRandom.getRandom().nextInt(countersOnCard.size());
return new ImmutablePair<CounterType,String>(countersOnCard.get(random),"Remove");
}
@Override
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));
byte chosenColorMask = MagicColor.fromName(c);
if ((colors.getColor() & chosenColorMask) != 0) {
return chosenColorMask;
} else {
return Iterables.getFirst(colors, (byte)0);
}
}
@Override
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
List<Card> hand = new ArrayList<Card>(player.getCardsIn(ZoneType.Hand));
if( sa.getApi() == ApiType.Mana )
hand.addAll(player.getCardsIn(ZoneType.Stack));
final String c = ComputerUtilCard.getMostProminentColor(hand);
byte chosenColorMask = MagicColor.fromName(c);
if ((colors.getColor() & chosenColorMask) != 0) {
return chosenColorMask;
} else {
return Iterables.getFirst(colors, MagicColor.WHITE);
}
}
@Override
public PaperCard chooseSinglePaperCard(SpellAbility sa, String message,
Predicate<PaperCard> cpp, String name) {
throw new UnsupportedOperationException("Should not be called for AI"); // or implement it if you know how
}
@Override
public List<String> chooseColors(String message, SpellAbility sa, int min, int max, List<String> options) {
return ComputerUtilCard.chooseColor(sa, min, max, options);
}
@Override
public CounterType chooseCounterType(Collection<CounterType> options, SpellAbility sa, String prompt) {
// may write a smarter AI if you need to (with calls to AI-clas for given API ability)
// TODO: ArsenalNut (06 Feb 12)computer needs
// better logic to pick a counter type and probably
// an initial target
// find first nonzero counter on target
return Iterables.getFirst(options, null);
}
@Override
public boolean confirmPayment(CostPart costPart, String prompt) {
return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method)
}
@Override
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers, HashMap<String, Object> runParams) {
// AI logic for choosing which replacement effect to apply
// happens here.
return possibleReplacers.get(0);
}
@Override
public String chooseProtectionType(String string, SpellAbility sa, List<String> choices) {
String choice = choices.get(0);
final String logic = sa.getParam("AILogic");
if (logic == null || logic.equals("MostProminentHumanCreatures")) {
List<Card> list = new ArrayList<Card>();
for (Player opp : player.getOpponents()) {
list.addAll(opp.getCreaturesInPlay());
}
if (list.isEmpty()) {
list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());
}
if (!list.isEmpty()) {
choice = ComputerUtilCard.getMostProminentColor(list);
}
}
return choice;
}
@Override
public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, List<Player> allPayers) {
final Card source = sa.getHostCard();
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
emptyAbility.setActivatingPlayer(player);
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
ComputerUtil.playNoStack(player, emptyAbility, game); // AI needs something to resolve to pay that cost
return true;
}
return false;
}
@Override
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
for (final SpellAbility sa : activePlayerSAs) {
prepareSingleSa(sa.getHostCard(),sa,true);
ComputerUtil.playStack(sa, player, game);
}
}
private void prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){
if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
targetingPlayer.getController().chooseTargetsFor(sa);
} else {
brains.doTrigger(sa, isMandatory);
}
}
@Override
public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) {
prepareSingleSa(host, wrapperAbility, isMandatory);
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, game);
}
@Override
public boolean playSaFromPlayEffect(SpellAbility tgtSA) {
boolean optional = tgtSA.hasParam("Optional");
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
Spell spell = (Spell) tgtSA;
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
if (noManaCost) {
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
} else {
ComputerUtil.playStack(tgtSA, player, game);
}
} else
return false; // didn't play spell
}
return true;
}
@Override
public Map<GameEntity, CounterType> chooseProliferation() {
return brains.chooseProliferation();
}
@Override
public boolean chooseTargetsFor(SpellAbility currentAbility) {
return brains.doTrigger(currentAbility, true);
}
@Override
public boolean chooseCardsPile(SpellAbility sa, List<Card> pile1, List<Card> pile2, boolean faceUp) {
if (!faceUp) {
// AI will choose the first pile if it is larger or the same
// TODO Improve this to be slightly more random to not be so predictable
return pile1.size() >= pile2.size();
} else {
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
System.out.println("value:" + cmc1 + " " + cmc2);
// for now, this assumes that the outcome will be bad
// TODO: This should really have a ChooseLogic param to
// figure this out
return "Worst".equals(sa.getParam("AILogic")) ^ (cmc1 >= cmc2);
}
}
@Override
public void revealAnte(String message, Multimap<Player, PaperCard> removedAnteCards) {
// Ai won't understand that anyway
}
@Override
public Collection<? extends PaperCard> complainCardsCantPlayWell(Deck myDeck) {
return brains.complainCardsCantPlayWell(myDeck);
}
@Override
public List<Card> cheatShuffle(List<Card> list) {
return brains.getBooleanProperty(AiProps.CHEAT_WITH_MANA_ON_SHUFFLE) ? brains.cheatShuffle(list) : list;
}
@Override
public CardShields chooseRegenerationShield(Card c) {
return Iterables.getFirst(c.getShield(), null);
}
@Override
public List<PaperCard> chooseCardsYouWonToAddToDeck(List<PaperCard> losses) {
// TODO AI takes all by default
return losses;
}
@Override
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */ ) {
// TODO Auto-generated method stub
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, false, 0);
return ComputerUtilMana.payManaCost(cost, sa, player);
}
@Override
public Map<Card, ManaCostShard> chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost,
List<Card> untappedCreats) {
// TODO: AI to choose a creature to tap would go here
// Probably along with deciding how many creatures to tap
return new HashMap<Card, ManaCostShard>();
}
@Override
public String chooseCardName(SpellAbility sa, Predicate<PaperCard> cpp, String valid, String message) {
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if (logic.equals("MostProminentInComputerDeck")) {
return ComputerUtilCard.getMostProminentCardName(player.getCardsIn(ZoneType.Library));
} else if (logic.equals("MostProminentInHumanDeck")) {
return ComputerUtilCard.getMostProminentCardName(player.getOpponent().getCardsIn(ZoneType.Library));
} else if (logic.equals("BestCreatureInComputerDeck")) {
return ComputerUtilCard.getBestCreatureAI(player.getCardsIn(ZoneType.Library)).getName();
} else if (logic.equals("RandomInComputerDeck")) {
return Aggregates.random(player.getCardsIn(ZoneType.Library)).getName();
}
} else {
List<Card> list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponent());
list = CardLists.filter(list, Predicates.not(Presets.LANDS));
if (!list.isEmpty()) {
return list.get(0).getName();
}
}
return "Morphling";
}
@Override
public Card chooseSingleCardForZoneChange(ZoneType destination,
List<ZoneType> origin, SpellAbility sa, List<Card> fetchList,
String selectPrompt, boolean b, Player decider) {
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player, decider);
}
}

View File

@@ -0,0 +1,175 @@
package forge.ai;
import com.google.common.collect.Iterables;
import forge.game.GameEntity;
import forge.game.ability.SaTargetRoutines;
import forge.game.card.Card;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import java.util.Collection;
import java.util.List;
public abstract class SpellAbilityAi extends SaTargetRoutines {
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
if (!canPlayAI(aiPlayer, sa)) {
return false;
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
}
protected abstract boolean canPlayAI(final Player aiPlayer, final SpellAbility sa);
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
return false;
}
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
}
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
{
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
return false;
}
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 (canPlayAI(aiPlayer, sa)) {
return true;
}
if (mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = aiPlayer.getOpponent();
if (tgt != null) {
if (opp.canBeTargetedBy(sa)) {
sa.resetTargets();
sa.getTargets().add(opp);
} else if (mandatory) {
if (aiPlayer.canBeTargetedBy(sa)) {
sa.resetTargets();
sa.getTargets().add(opp);
} else {
return false;
}
} else {
return false;
}
}
return true;
}
return false;
}
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
return true;
}
/**
* <p>
* isSorcerySpeed.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
protected static boolean isSorcerySpeed(final SpellAbility sa) {
return ( sa.isSpell() && sa.getHostCard().isSorcery() )
|| ( sa.isAbility() && sa.getRestrictions().isSorcerySpeed() );
}
/**
* <p>
* playReusable.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
// TODO probably also consider if winter orb or similar are out
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().getPlaneswalker() && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
return true;
}
if (sa.isTrigger()) {
return true;
}
if (sa.isSpell() && !sa.isBuyBackAbility()) {
return false;
}
PhaseHandler phase = ai.getGame().getPhaseHandler();
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
}
/**
* TODO: Write javadoc for this method.
* @param ai
* @param subAb
* @return
*/
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
final AbilitySub subAb = ab.getSubAbility();
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
}
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return true;
}
@SuppressWarnings("unchecked")
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
T firstOption = Iterables.getFirst(options, null);
if( firstOption instanceof Card)
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
else if ( firstOption instanceof Player)
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
else
return null;
}
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 for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return spells.get(0);
}
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleEntity is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleEntity is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
}

View File

@@ -0,0 +1,153 @@
package forge.ai;
import java.util.EnumMap;
import forge.ai.ability.*;
import forge.game.ability.ApiType;
import forge.util.ReflectionUtil;
public enum SpellApiToAi {
Converter;
private final static EnumMap<ApiType, Class<? extends SpellAbilityAi>> apiToClass = new EnumMap<>(ApiType.class);
private final EnumMap<ApiType, SpellAbilityAi> apiToInstance = new EnumMap<>(ApiType.class);
static {
apiToClass.put(ApiType.Abandon, AlwaysPlayAi.class);
apiToClass.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class);
apiToClass.put(ApiType.AddPhase, AddPhaseAi.class);
apiToClass.put(ApiType.AddTurn, AddTurnAi.class);
apiToClass.put(ApiType.Animate, AnimateAi.class);
apiToClass.put(ApiType.AnimateAll, AnimateAllAi.class);
apiToClass.put(ApiType.Attach, AttachAi.class);
apiToClass.put(ApiType.Balance, BalanceAi.class);
apiToClass.put(ApiType.BecomesBlocked, BecomesBlockedAi.class);
apiToClass.put(ApiType.Bond, BondAi.class);
apiToClass.put(ApiType.ChangeTargets, ChangeTargetsAi.class);
apiToClass.put(ApiType.ChangeZone, ChangeZoneAi.class);
apiToClass.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class);
apiToClass.put(ApiType.Charm, CharmAi.class);
apiToClass.put(ApiType.ChooseCard, ChooseCardAi.class);
apiToClass.put(ApiType.ChooseColor, ChooseColorAi.class);
apiToClass.put(ApiType.ChooseNumber, CannotPlayAi.class);
apiToClass.put(ApiType.ChoosePlayer, ChoosePlayerAi.class);
apiToClass.put(ApiType.ChooseSource, ChooseSourceAi.class);
apiToClass.put(ApiType.ChooseType, ChooseTypeAi.class);
apiToClass.put(ApiType.Clash, ClashAi.class);
apiToClass.put(ApiType.Cleanup, AlwaysPlayAi.class);
apiToClass.put(ApiType.Clone, CloneAi.class);
apiToClass.put(ApiType.CopyPermanent, CopyPermanentAi.class);
apiToClass.put(ApiType.CopySpellAbility, CanPlayAsDrawbackAi.class);
apiToClass.put(ApiType.ControlPlayer, CannotPlayAi.class);
apiToClass.put(ApiType.ControlSpell, CannotPlayAi.class);
apiToClass.put(ApiType.Counter, CounterAi.class);
apiToClass.put(ApiType.DamageAll, DamageAllAi.class);
apiToClass.put(ApiType.DealDamage, DamageDealAi.class);
apiToClass.put(ApiType.Debuff, DebuffAi.class);
apiToClass.put(ApiType.DebuffAll, DebuffAllAi.class);
apiToClass.put(ApiType.DeclareCombatants, CannotPlayAi.class);
apiToClass.put(ApiType.DelayedTrigger, DelayedTriggerAi.class);
apiToClass.put(ApiType.Destroy, DestroyAi.class);
apiToClass.put(ApiType.DestroyAll, DestroyAllAi.class);
apiToClass.put(ApiType.Dig, DigAi.class);
apiToClass.put(ApiType.DigUntil, DigUntilAi.class);
apiToClass.put(ApiType.Discard, DiscardAi.class);
apiToClass.put(ApiType.DrainMana, DrainManaAi.class);
apiToClass.put(ApiType.Draw, DrawAi.class);
apiToClass.put(ApiType.EachDamage, DamageEachAi.class);
apiToClass.put(ApiType.Effect, EffectAi.class);
apiToClass.put(ApiType.Encode, EncodeAi.class);
apiToClass.put(ApiType.EndTurn, EndTurnAi.class);
apiToClass.put(ApiType.ExchangeLife, LifeExchangeAi.class);
apiToClass.put(ApiType.ExchangeControl, ControlExchangeAi.class);
apiToClass.put(ApiType.ExchangePower, PowerExchangeAi.class);
apiToClass.put(ApiType.ExchangeZone, ZoneExchangeAi.class);
apiToClass.put(ApiType.Fight, FightAi.class);
apiToClass.put(ApiType.FlipACoin, FlipACoinAi.class);
apiToClass.put(ApiType.Fog, FogAi.class);
apiToClass.put(ApiType.GainControl, ControlGainAi.class);
apiToClass.put(ApiType.GainLife, LifeGainAi.class);
apiToClass.put(ApiType.GainOwnership, CannotPlayAi.class);
apiToClass.put(ApiType.GenericChoice, ChooseGenericEffectAi.class);
apiToClass.put(ApiType.LoseLife, LifeLoseAi.class);
apiToClass.put(ApiType.LosesGame, GameLossAi.class);
apiToClass.put(ApiType.Mana, ManaEffectAi.class);
apiToClass.put(ApiType.ManaReflected, CannotPlayAi.class);
apiToClass.put(ApiType.Mill, MillAi.class);
apiToClass.put(ApiType.MoveCounter, CountersMoveAi.class);
apiToClass.put(ApiType.MultiplePiles, CannotPlayAi.class);
apiToClass.put(ApiType.MustAttack, MustAttackAi.class);
apiToClass.put(ApiType.MustBlock, MustBlockAi.class);
apiToClass.put(ApiType.NameCard, ChooseCardNameAi.class);
apiToClass.put(ApiType.PeekAndReveal, PeekAndRevealAi.class);
apiToClass.put(ApiType.PermanentCreature, PermanentCreatureAi.class);
apiToClass.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class);
apiToClass.put(ApiType.Phases, PhasesAi.class);
apiToClass.put(ApiType.Planeswalk, AlwaysPlayAi.class);
apiToClass.put(ApiType.Play, PlayAi.class);
apiToClass.put(ApiType.Poison, PoisonAi.class);
apiToClass.put(ApiType.PreventDamage, DamagePreventAi.class);
apiToClass.put(ApiType.PreventDamageAll, DamagePreventAllAi.class);
apiToClass.put(ApiType.Proliferate, CountersProliferateAi.class);
apiToClass.put(ApiType.Protection, ProtectAi.class);
apiToClass.put(ApiType.ProtectionAll, ProtectAllAi.class);
apiToClass.put(ApiType.Pump, PumpAi.class);
apiToClass.put(ApiType.PumpAll, PumpAllAi.class);
apiToClass.put(ApiType.PutCounter, CountersPutAi.class);
apiToClass.put(ApiType.PutCounterAll, CountersPutAllAi.class);
apiToClass.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class);
apiToClass.put(ApiType.Regenerate, RegenerateAi.class);
apiToClass.put(ApiType.RegenerateAll, RegenerateAllAi.class);
apiToClass.put(ApiType.RemoveCounter, CountersRemoveAi.class);
apiToClass.put(ApiType.RemoveCounterAll, CannotPlayAi.class);
apiToClass.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class);
apiToClass.put(ApiType.ReorderZone, AlwaysPlayAi.class);
apiToClass.put(ApiType.Repeat, RepeatAi.class);
apiToClass.put(ApiType.RepeatEach, RepeatEachAi.class);
apiToClass.put(ApiType.RestartGame, RestartGameAi.class);
apiToClass.put(ApiType.Reveal, RevealAi.class);
apiToClass.put(ApiType.RevealHand, RevealHandAi.class);
apiToClass.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class);
apiToClass.put(ApiType.RunSVarAbility, AlwaysPlayAi.class);
apiToClass.put(ApiType.Sacrifice, SacrificeAi.class);
apiToClass.put(ApiType.SacrificeAll, SacrificeAllAi.class);
apiToClass.put(ApiType.Scry, ScryAi.class);
apiToClass.put(ApiType.SetInMotion, AlwaysPlayAi.class);
apiToClass.put(ApiType.SetLife, LifeSetAi.class);
apiToClass.put(ApiType.SetState, SetStateAi.class);
apiToClass.put(ApiType.Shuffle, ShuffleAi.class);
apiToClass.put(ApiType.SkipTurn, CannotPlayAi.class);
apiToClass.put(ApiType.StoreSVar, StoreSVarAi.class);
apiToClass.put(ApiType.Tap, TapAi.class);
apiToClass.put(ApiType.TapAll, TapAllAi.class);
apiToClass.put(ApiType.TapOrUntap, TapOrUntapAi.class);
apiToClass.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class);
apiToClass.put(ApiType.Token, TokenAi.class);
apiToClass.put(ApiType.TwoPiles, TwoPilesAi.class);
apiToClass.put(ApiType.Unattach, CannotPlayAi.class);
apiToClass.put(ApiType.UnattachAll, UnattachAllAi.class);
apiToClass.put(ApiType.Untap, UntapAi.class);
apiToClass.put(ApiType.UntapAll, UntapAllAi.class);
apiToClass.put(ApiType.WinsGame, GameWinAi.class);
apiToClass.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class);
apiToClass.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class);
apiToClass.put(ApiType.InternalHaunt, HauntAi.class);
}
public SpellAbilityAi get(ApiType api) {
SpellAbilityAi result = apiToInstance.get(api);
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

@@ -0,0 +1,18 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class AddPhaseAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
}

View File

@@ -15,16 +15,15 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.card.ability.ai;
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.List;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.player.Player;
/**
* <p>
* AbilityFactory_Turns class.
@@ -39,27 +38,26 @@ public class AddTurnAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent();
final Target tgt = sa.getTarget();
if (sa.getTarget() != null) {
tgt.resetTargets();
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(ai)) {
sa.getTarget().addTarget(ai);
sa.getTargets().add(ai);
} else if (mandatory) {
for (final Player ally : ai.getAllies()) {
if (sa.canTarget(ally)) {
sa.getTarget().addTarget(ally);
sa.getTargets().add(ally);
break;
}
}
if (!sa.getTarget().isMinTargetsChosen(sa.getSourceCard(), sa) && sa.canTarget(opp)) {
sa.getTarget().addTarget(opp);
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
return false;
}
}
} else {
final List<Player> tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa);
final List<Player> tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
for (final Player p : tgtPlayers) {
if (p.isOpponentOf(ai) && !mandatory) {
return false;

View File

@@ -0,0 +1,16 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class AlwaysPlayAi 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) {
return true;
}
}

View File

@@ -1,20 +1,20 @@
package forge.card.ability.ai;
import java.util.List;
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.Card;
import forge.CardPredicates;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
/**
* <p>
* AbilityFactoryAnimate class.
@@ -31,9 +31,10 @@ public class AnimateAi extends SpellAbilityAi {
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Target tgt = sa.getTarget();
final Card source = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Game game = aiPlayer.getGame();
final PhaseHandler ph = game.getPhaseHandler();
// TODO - add some kind of check to answer
// "Am I going to attack with this?"
@@ -42,8 +43,8 @@ public class AnimateAi extends SpellAbilityAi {
// don't use instant speed animate abilities outside computers
// Combat_Begin step
if (!game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN)
&& game.getPhaseHandler().isPlayerTurn(aiPlayer)
if (!ph.is(PhaseType.COMBAT_BEGIN)
&& ph.isPlayerTurn(aiPlayer)
&& !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
return false;
@@ -51,7 +52,7 @@ public class AnimateAi extends SpellAbilityAi {
Player opponent = aiPlayer.getWeakestOpponent();
// don't animate if the AI won't attack anyway
if (game.getPhaseHandler().isPlayerTurn(aiPlayer)
if (ph.isPlayerTurn(aiPlayer)
&& aiPlayer.getLife() < 6
&& opponent.getLife() > 6
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
@@ -60,14 +61,13 @@ public class AnimateAi extends SpellAbilityAi {
// don't use instant speed animate abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if ((!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| (game.getCombat().getAttackers().isEmpty()))
&& game.getPhaseHandler().isPlayerTurn(opponent)) {
if (ph.getPlayerTurn().isOpponentOf(aiPlayer) &&
(!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS, opponent) || game.getCombat().getAttackersOf(aiPlayer).isEmpty())) {
return false;
}
// don't activate during main2 unless this effect is permanent
if (game.getPhaseHandler().is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
return false;
}
@@ -77,7 +77,7 @@ public class AnimateAi extends SpellAbilityAi {
boolean bFlag = false;
if (sa.hasParam("AILogic")) {
if ("EOT".equals(sa.getParam("AILogic"))) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
if (ph.getPhase().isBefore(PhaseType.MAIN2)) {
return false;
} else {
bFlag = true;
@@ -112,7 +112,7 @@ public class AnimateAi extends SpellAbilityAi {
return false;
}
} else {
tgt.resetTargets();
sa.resetTargets();
if (!animateTgtAI(sa)) {
return false;
}
@@ -125,8 +125,8 @@ public class AnimateAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
if (sa.getTarget() != null) {
sa.getTarget().resetTargets();
if (sa.usesTargeting()) {
sa.resetTargets();
if (!animateTgtAI(sa)) {
return false;
}
@@ -140,18 +140,18 @@ public class AnimateAi extends SpellAbilityAi {
* animateTriggerAI.
* </p>
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.card.ability.AbilityFactory} object.
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.getTarget() != null && !animateTgtAI(sa) && !mandatory) {
if (sa.usesTargeting() && !animateTgtAI(sa) && !mandatory) {
return false;
}
@@ -171,9 +171,9 @@ public class AnimateAi extends SpellAbilityAi {
* </p>
*
* @param af
* a {@link forge.card.ability.AbilityFactory} object.
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
private boolean animateTgtAI(final SpellAbility sa) {

View File

@@ -0,0 +1,19 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class AnimateAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
} // end animateAllCanPlayAI()
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
} // end class AbilityFactoryAnimate

View File

@@ -1,39 +1,27 @@
package forge.card.ability.ai;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.Card;
import forge.CardLists;
import forge.CardPredicates;
import forge.CardUtil;
import forge.card.ability.AbilityUtils;
import forge.card.ability.ApiType;
import forge.card.ability.SpellAbilityAi;
import forge.card.cardfactory.CardFactoryUtil;
import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.card.staticability.StaticAbility;
import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana;
import forge.game.phase.CombatUtil;
import forge.ai.*;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.zone.ZoneType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.util.MyRandom;
import java.util.*;
public class AttachAi extends SpellAbilityAi {
/* (non-Javadoc)
@@ -43,7 +31,7 @@ public class AttachAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
if (abCost != null) {
// AI currently disabled for these costs
@@ -59,9 +47,9 @@ public class AttachAi extends SpellAbilityAi {
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// Attach spells always have a target
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
tgt.resetTargets();
sa.resetTargets();
if (!attachPreference(sa, tgt, false)) {
return false;
}
@@ -166,7 +154,7 @@ public class AttachAi extends SpellAbilityAi {
String type = "";
for (final StaticAbility stAb : attachSource.getStaticAbilities()) {
final HashMap<String, String> stab = stAb.getMapParams();
final Map<String, String> stab = stAb.getMapParams();
if (stab.get("Mode").equals("Continuous") && stab.containsKey("AddType")) {
type = stab.get("AddType");
}
@@ -331,7 +319,7 @@ public class AttachAi extends SpellAbilityAi {
final Card attachSource) {
// AI For choosing a Card to Animate.
List<Card> betterList = CardLists.getNotType(list, "Creature");
if (sa.getSourceCard().getName().equals("Animate Artifact")) {
if (sa.getHostCard().getName().equals("Animate Artifact")) {
betterList = CardLists.filter(betterList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
@@ -397,7 +385,7 @@ public class AttachAi extends SpellAbilityAi {
// I know this isn't much better than Hardcoding, but some cards need it for now
final Player ai = sa.getActivatingPlayer();
Card chosen = null;
if ("Guilty Conscience".equals(sa.getSourceCard().getName())) {
if ("Guilty Conscience".equals(sa.getHostCard().getName())) {
List<Card> aiStuffies = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
@@ -454,7 +442,7 @@ public class AttachAi extends SpellAbilityAi {
final Card attachSource) {
// AI For choosing a Card to Gain Control of.
if (sa.getTarget().canTgtPermanent()) {
if (sa.getTargetRestrictions().canTgtPermanent()) {
// If can target all Permanents, and Life isn't in eminent danger,
// grab Planeswalker first, then Creature
// if Life < 5 grab Creature first, then Planeswalker. Lands,
@@ -561,7 +549,7 @@ public class AttachAi extends SpellAbilityAi {
}
Card c = null;
if ((prefList == null) || prefList.isEmpty()) {
if (prefList == null || prefList.isEmpty()) {
prefList = new ArrayList<Card>(list);
} else {
c = ComputerUtilCard.getBestAI(prefList);
@@ -586,6 +574,20 @@ public class AttachAi extends SpellAbilityAi {
});
}
//some auras aren't useful in multiples
if (attachSource.hasSVar("NonStackingAttachEffect")) {
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (Card aura : c.getEnchantedBy()) {
if (aura.getName().equals(attachSource.getName()))
return false;
}
return true;
}
});
}
c = ComputerUtilCard.getBestAI(prefList);
if (c == null) {
@@ -609,14 +611,15 @@ public class AttachAi extends SpellAbilityAi {
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Card card = sa.getSourceCard();
final Card card = sa.getHostCard();
// Check if there are any valid targets
ArrayList<Object> targets = new ArrayList<Object>();
final Target tgt = sa.getTarget();
List<GameObject> targets = new ArrayList<GameObject>();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
targets = AbilityUtils.getDefinedObjects(sa.getSourceCard(), sa.getParam("Defined"), sa);
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
} else {
AttachAi.attachPreference(sa, tgt, mandatory);
targets = sa.getTargets().getTargets();
}
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
@@ -632,6 +635,13 @@ public class AttachAi extends SpellAbilityAi {
if (ComputerUtilCard.evaluateCreature(oldTarget) > ComputerUtilCard.evaluateCreature(newTarget)) {
return false;
}
// don't equip creatures that don't gain anything
if (card.hasSVar("NonStackingAttachEffect")) {
for (Card equipment : newTarget.getEquippedBy()) {
if (equipment.getName().equals(card.getName()))
return false;
}
}
}
}
@@ -677,8 +687,8 @@ public class AttachAi extends SpellAbilityAi {
* the mandatory
* @return true, if successful
*/
private static boolean attachPreference(final SpellAbility sa, final Target tgt, final boolean mandatory) {
Object o;
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
GameObject o;
if (tgt.canTgtPlayer()) {
o = attachToPlayerAIPreferences(sa.getActivatingPlayer(), sa, mandatory);
} else {
@@ -689,7 +699,7 @@ public class AttachAi extends SpellAbilityAi {
return false;
}
tgt.addTarget(o);
sa.getTargets().add(o);
return true;
}
@@ -712,7 +722,7 @@ public class AttachAi extends SpellAbilityAi {
Card c = null;
List<Card> magnetList = null;
String stCheck = null;
if (attachSource.isAura()) {
if (attachSource.isAura() || sa.hasParam("Bestow")) {
stCheck = "EnchantedBy";
magnetList = CardLists.filter(list, new Predicate<Card>() {
@Override
@@ -736,21 +746,47 @@ public class AttachAi extends SpellAbilityAi {
return sVar.equals("Multiple") || (sVar.equals("Once") && !c.isEquipped());
}
});
}
if ((magnetList != null) && !magnetList.isEmpty()) {
// Always choose something from the Magnet List.
// Probably want to "weight" the list by amount of Enchantments and
// choose the "lightest"
magnetList = CardLists.filter(magnetList, new Predicate<Card>() {
} else if (attachSource.isFortification()) {
stCheck = "FortifiedBy";
magnetList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, ai.getWeakestOpponent());
return c.isCreature() && !c.isFortified();
}
});
}
return ComputerUtilCard.getBestAI(magnetList);
if (magnetList != null) {
// Look for Heroic triggers
if (magnetList.isEmpty() && sa.isSpell()) {
for (Card target : list) {
for (Trigger t : target.getTriggers()) {
if (t.getMode() == TriggerType.SpellCast) {
final Map<String, String> params = t.getMapParams();
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
magnetList.add(target);
break;
}
}
}
}
}
if (!magnetList.isEmpty()) {
// Always choose something from the Magnet List.
// Probably want to "weight" the list by amount of Enchantments and
// choose the "lightest"
magnetList = CardLists.filter(magnetList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, ai.getWeakestOpponent());
}
});
return ComputerUtilCard.getBestAI(magnetList);
}
}
int totToughness = 0;
@@ -761,7 +797,7 @@ public class AttachAi extends SpellAbilityAi {
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
final Map<String, String> stabMap = stAbility.getMapParams();
if (!stabMap.get("Mode").equals("Continuous")) {
if (!"Continuous".equals(stabMap.get("Mode"))) {
continue;
}
@@ -809,7 +845,30 @@ public class AttachAi extends SpellAbilityAi {
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return containsUsefulKeyword(keywords, c, sa, pow);
for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
return true;
}
}
return false;
}
});
}
//some auras/equipments aren't useful in multiples
if (attachSource.hasSVar("NonStackingAttachEffect")) {
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (Card equipment : c.getEquippedBy()) {
if (equipment.getName().equals(attachSource.getName()))
return false;
}
for (Card aura : c.getEnchantedBy()) {
if (aura.getName().equals(attachSource.getName()))
return false;
}
return true;
}
});
}
@@ -841,6 +900,11 @@ public class AttachAi extends SpellAbilityAi {
}
c = ComputerUtilCard.getBestAI(prefList);
} else {
for (Card pref : prefList) {
if (pref.isLand() && pref.isUntapped()) {
return pref;
}
}
// If we grant abilities, we may want to put it on something Weak?
// Possibly more defensive?
c = ComputerUtilCard.getWorstPermanentAI(prefList, false, false, false, false);
@@ -865,13 +929,17 @@ public class AttachAi extends SpellAbilityAi {
* @return the card
*/
private static Card attachToCardAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
final Target tgt = sa.getTarget();
final Card attachSource = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card attachSource = sa.getHostCard();
// TODO AttachSource is currently set for the Source of the Spell, but
// at some point can support attaching a different card
// Don't equip if already equipping
if (attachSource.getEquippingCard() != null && attachSource.getEquippingCard().getController() == aiPlayer) {
if (attachSource.getEquippingCard() != null && attachSource.getEquippingCard().getController() == aiPlayer || attachSource.hasSVar("DontEquip")) {
return null;
}
// Don't fortify if already fortifying
if (attachSource.getFortifyingCard() != null && attachSource.getFortifyingCard().getController() == aiPlayer) {
return null;
}
@@ -882,7 +950,7 @@ public class AttachAi extends SpellAbilityAi {
// I believe this is the only case where mandatory will be true, so just
// check that when starting that work
// But we shouldn't attach to things with Protection
if (tgt.getZone().contains(ZoneType.Battlefield) && !mandatory) {
if (!mandatory) {
list = CardLists.getTargetableCards(list, sa);
} else {
list = CardLists.filter(list, Predicates.not(CardPredicates.isProtectedFrom(attachSource)));
@@ -969,25 +1037,6 @@ public class AttachAi extends SpellAbilityAi {
return c;
}
/**
* Contains useful keyword.
*
* @param keywords
* the keywords
* @param card
* the card
* @param sa SpellAbility
* @return true, if successful
*/
private static boolean containsUsefulKeyword(final ArrayList<String> keywords, final Card card, final SpellAbility sa, final int powerBonus) {
for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, card, sa, powerBonus)) {
return true;
}
}
return false;
}
/**
* Contains useful curse keyword.
*
@@ -1018,21 +1067,23 @@ public class AttachAi extends SpellAbilityAi {
* @return true, if is useful keyword
*/
private static boolean isUsefulAttachKeyword(final String keyword, final Card card, final SpellAbility sa, final int powerBonus) {
final PhaseHandler ph = sa.getActivatingPlayer().getGame().getPhaseHandler();
final Player ai = sa.getActivatingPlayer();
final Player opponent = ai.getOpponent();
final PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
return false;
}
final boolean evasive = (keyword.equals("Unblockable") || keyword.equals("Fear")
|| keyword.equals("Intimidate") || keyword.equals("Shadow")
|| keyword.equals("Flying") || keyword.equals("Horsemanship")
|| keyword.endsWith("walk") || keyword.equals("CARDNAME can't be blocked except by Walls.")
|| keyword.equals("All creatures able to block CARDNAME do so.")
|| keyword.equals("CARDNAME can't be blocked by more than one creature."));
|| keyword.endsWith("walk") || keyword.startsWith("CantBeBlockedBy")
|| keyword.equals("All creatures able to block CARDNAME do so."));
// give evasive keywords to creatures that can attack and deal damage
if (evasive) {
if (card.getNetCombatDamage() + powerBonus <= 0
|| !CombatUtil.canAttackNextTurn(card)
|| !CombatUtil.canBeBlocked(card)) {
|| !CombatUtil.canBeBlocked(card, opponent)) {
return false;
}
} else if (keyword.equals("Haste")) {
@@ -1047,7 +1098,7 @@ public class AttachAi extends SpellAbilityAi {
return true;
} else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) {
if (card.getNetCombatDamage() + powerBonus <= 0
|| ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card))
|| ((!CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card))
&& !CombatUtil.canBlock(card, true))) {
return false;
}
@@ -1063,17 +1114,17 @@ public class AttachAi extends SpellAbilityAi {
} else if (keyword.startsWith("Flanking")) {
if (card.getNetCombatDamage() + powerBonus <= 0
|| !CombatUtil.canAttackNextTurn(card)
|| !CombatUtil.canBeBlocked(card)) {
|| !CombatUtil.canBeBlocked(card, opponent)) {
return false;
}
} else if (keyword.startsWith("Bushido")) {
if ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card))
if ((!CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card))
&& !CombatUtil.canBlock(card, true)) {
return false;
}
} else if (keyword.equals("Trample")) {
if (card.getNetCombatDamage() + powerBonus <= 1
|| !CombatUtil.canBeBlocked(card)
|| !CombatUtil.canBeBlocked(card, opponent)
|| !CombatUtil.canAttackNextTurn(card)) {
return false;
}
@@ -1093,17 +1144,20 @@ public class AttachAi extends SpellAbilityAi {
return false;
}
} else if (keyword.endsWith("CARDNAME can block an additional creature.")) {
if (!CombatUtil.canBlock(card, true) || card.hasKeyword("CARDNAME can block any number of creatures.")) {
if (!CombatUtil.canBlock(card, true) || card.hasKeyword("CARDNAME can block any number of creatures.")
|| card.hasKeyword("CARDNAME can block an additional ninety-nine creatures.")) {
return false;
}
} else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) {
if (!card.hasKeyword("Defender") || card.hasKeyword("CARDNAME can attack as though it didn't have defender.")) {
if (!card.hasKeyword("Defender") || card.getNetCombatDamage() + powerBonus <= 0) {
return false;
}
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
if (card.hasKeyword("Shroud") || card.hasKeyword("Hexproof")) {
return false;
}
} else if (keyword.equals("Defender")) {
return false;
}
return true;
}
@@ -1167,12 +1221,12 @@ public class AttachAi extends SpellAbilityAi {
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
return attachToCardAIPreferences(ai, sa, true);
}
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, List<Player> options) {
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
return attachToPlayerAIPreferences(ai, sa, true);
}
}

View File

@@ -0,0 +1,52 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
public class BalanceAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
int diff = 0;
// TODO Add support for multiplayer logic
final Player opp = aiPlayer.getOpponent();
final List<Card> humPerms = opp.getCardsIn(ZoneType.Battlefield);
final List<Card> 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
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).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
diff += humPerms.size() - compPerms.size();
}
if (diff < 0) {
// Don't sacrifice permanents even if opponent has a ton of cards in hand
return false;
}
final List<Card> humHand = opp.getCardsIn(ZoneType.Hand);
final List<Card> 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;
}
}

View File

@@ -0,0 +1,77 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class BecomesBlockedAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = aiPlayer.getGame();
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
return false;
}
if (tgt != null) {
sa.resetTargets();
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.filterControlledBy(list, aiPlayer.getOpponents());
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getNotKeyword(list, "Trample");
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card choice = null;
if (list.isEmpty()) {
return false;
}
choice = ComputerUtilCard.getBestCreatureAI(list);
if (choice == null) { // can't find anything left
return false;
}
list.remove(choice);
sa.getTargets().add(choice);
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// TODO - implement AI
return false;
}
/* (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 aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false;
return chance;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.Collection;
/**
* <p>
* AbilityFactoryBond class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/
public final class BondAi extends SpellAbilityAi {
/**
* <p>
* bondCanPlayAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
} // end bondCanPlayAI()
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestCreatureAI(options);
}
}

View File

@@ -0,0 +1,44 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.List;
public class CanPlayAsDrawbackAi 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) {
return false;
}
/**
* <p>
* copySpellTriggerAI.
* </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(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
return spells.get(0);
}
}

View File

@@ -1,9 +1,9 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class CannotPlayAi extends SpellAbilityAi {
/* (non-Javadoc)

View File

@@ -0,0 +1,26 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChangeTargetsAi 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 aiPlayer, SpellAbility sa) {
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,22 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
import forge.Card;
import forge.CardLists;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ChangeZoneAllAi extends SpellAbilityAi {
/* (non-Javadoc)
@@ -26,9 +26,9 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// Change Zone All, can be any type moving from one zone to another
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.smartValueOf(sa.getParam("Origin"));
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (abCost != null) {
// AI currently disabled for these costs
@@ -57,7 +57,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
final List<Card> humanType = AbilityUtils.filterListByType(opp.getCardsIn(origin), sa.getParam("ChangeType"), sa);
List<Card> computerType = ai.getCardsIn(origin);
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
// TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
@@ -67,8 +67,8 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|| !opp.canBeTargetedBy(sa)) {
return false;
}
tgt.resetTargets();
tgt.addTarget(opp);
sa.resetTargets();
sa.getTargets().add(opp);
}
} else if (origin.equals(ZoneType.Battlefield)) {
// this statement is assuming the AI is trying to use this spell
@@ -82,8 +82,8 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|| !opp.canBeTargetedBy(sa)) {
return false;
}
tgt.resetTargets();
tgt.addTarget(opp);
sa.resetTargets();
sa.getTargets().add(opp);
computerType.clear();
}
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
@@ -108,8 +108,8 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|| !opp.canBeTargetedBy(sa)) {
return false;
}
tgt.resetTargets();
tgt.addTarget(opp);
sa.resetTargets();
sa.getTargets().add(opp);
}
} else if (origin.equals(ZoneType.Exile)) {
@@ -159,9 +159,9 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
* changeZoneAllPlayDrawbackAI.
* </p>
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* a {@link forge.game.spellability.SpellAbility} object.
* @param af
* a {@link forge.card.ability.AbilityFactory} object.
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@@ -189,14 +189,14 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
// TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
if (opp.getCardsIn(ZoneType.Hand).isEmpty()
|| !opp.canBeTargetedBy(sa)) {
return false;
}
tgt.resetTargets();
tgt.addTarget(opp);
sa.resetTargets();
sa.getTargets().add(opp);
}
} else if (origin.equals(ZoneType.Battlefield)) {
// this statement is assuming the AI is trying to use this spell offensively
@@ -215,14 +215,14 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
return false;
}
} else if (origin.equals(ZoneType.Graveyard)) {
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
if (opp.getCardsIn(ZoneType.Graveyard).isEmpty()
|| !opp.canBeTargetedBy(sa)) {
return false;
}
tgt.resetTargets();
tgt.addTarget(opp);
sa.resetTargets();
sa.getTargets().add(opp);
}
} else if (origin.equals(ZoneType.Exile)) {

View File

@@ -0,0 +1,99 @@
package forge.ai.ability;
import forge.ai.AiController;
import forge.ai.AiPlayDecision;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.effects.CharmEffect;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
import forge.util.MyRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
public class CharmAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Random r = MyRandom.getRandom();
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
List<AbilitySub> chosenList = chooseOptionsAi(sa, ai, timingRight, num, min, false);
if (chosenList.isEmpty()) {
return false;
}
// prevent run-away activations - first time will always return true
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
public static List<AbilitySub> chooseOptionsAi(SpellAbility sa, final Player ai, boolean playNow, int num, int min, boolean opponentChoser) {
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
List<AbilitySub> chosenList = new ArrayList<AbilitySub>();
if (opponentChoser) {
// 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.
//return choices.subList(0, 1);
return choices.subList(1, choices.size());
}
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
for (int i = 0; i < num; i++) {
AbilitySub thisPick = null;
for (SpellAbility sub : choices) {
sub.setActivatingPlayer(ai);
if (!playNow && AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
playNow = true;
break;
}
if ((playNow || i < num - 1) && aic.doTrigger(sub, false)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
break;
}
}
if (thisPick != null) {
chosenList.add(thisPick);
}
}
if (playNow && chosenList.size() < min) {
for (int i = 0; i < min; i++) {
AbilitySub thisPick = null;
for (SpellAbility sub : choices) {
sub.setActivatingPlayer(ai);
if (aic.doTrigger(sub, true)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
break;
}
}
if (thisPick != null) {
chosenList.add(thisPick);
}
}
}
return chosenList;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> opponents) {
return Aggregates.random(opponents);
}
}

View File

@@ -0,0 +1,188 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class ChooseCardAi 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(final Player ai, SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = ai.getGame();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(ai.getOpponent())) {
sa.getTargets().add(ai.getOpponent());
} else {
return false;
}
}
if (sa.hasParam("AILogic")) {
ZoneType choiceZone = ZoneType.Battlefield;
String logic = sa.getParam("AILogic");
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
List<Card> choices = ai.getGame().getCardsIn(choiceZone);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
}
if (sa.hasParam("TargetControls")) {
choices = CardLists.filterControlledBy(choices, ai.getOpponent());
}
if (logic.equals("AtLeast1") || logic.equals("OppPreferred")) {
if (choices.isEmpty()) {
return false;
}
} else if (logic.equals("AtLeast2") || logic.equals("BestBlocker")) {
if (choices.size() < 2) {
return false;
}
} else if (logic.equals("Clone")) {
choices = CardLists.getValidCards(choices, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host);
if (choices.isEmpty()) {
return false;
}
} else if (logic.equals("Never")) {
return false;
} else if (logic.equals("NeedsPrevention")) {
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = host.getName().equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
}
});
if (choices.isEmpty()) {
return false;
}
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
final Card host = sa.getHostCard();
final String logic = sa.getParam("AILogic");
Card choice = null;
if (logic == null) {
// Base Logic is choose "best"
choice = ComputerUtilCard.getBestAI(options);
} else if ("WorstCard".equals(logic)) {
choice = ComputerUtilCard.getWorstAI(options);
} else if (logic.equals("BestBlocker")) {
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
options = CardLists.filter(options, Presets.UNTAPPED);
}
choice = ComputerUtilCard.getBestCreatureAI(options);
} else if (logic.equals("Clone")) {
if (!CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host).isEmpty()) {
options = CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host);
}
choice = ComputerUtilCard.getBestAI(options);
} else if (logic.equals("Untap")) {
if (!CardLists.getValidCards(options, "Permanent.YouCtrl,Permanent.tapped", host.getController(), host).isEmpty()) {
options = CardLists.getValidCards(options, "Permanent.YouCtrl,Permanent.tapped", host.getController(), host);
}
choice = ComputerUtilCard.getBestAI(options);
} else if (logic.equals("NeedsPrevention")) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
List<Card> better = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = host.getName().equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
}
});
if (!better.isEmpty()) {
choice = ComputerUtilCard.getBestAI(better);
} else {
choice = ComputerUtilCard.getBestAI(options);
}
} else if ("OppPreferred".equals(logic)) {
List<Card> oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
if (!oppControlled.isEmpty()) {
choice = ComputerUtilCard.getBestAI(oppControlled);
} else {
List<Card> aiControlled = CardLists.filterControlledBy(options, ai);
choice = ComputerUtilCard.getWorstAI(aiControlled);
}
} else if ("LowestCMCCreature".equals(logic)) {
List<Card> creats = CardLists.filter(options, Presets.CREATURES);
creats = CardLists.filterToughness(creats, 1);
if (creats.isEmpty()) {
choice = ComputerUtilCard.getWorstAI(options);
} else {
CardLists.sortByCmcDesc(creats);
Collections.reverse(creats);
choice = creats.get(0);
}
} else if ("TangleWire".equals(logic)) {
List<Card> betterList = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.isCreature()) {
return false;
}
for (SpellAbility sa : c.getAllSpellAbilities()) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
return false;
}
}
return true;
}
});
System.out.println("Tangle Wire" + options + " - " + betterList);
if (!betterList.isEmpty()) {
choice = betterList.get(0);
} else {
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
}
} else {
choice = ComputerUtilCard.getBestAI(options);
}
return choice;
}
}

View File

@@ -0,0 +1,63 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class ChooseCardNameAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Card source = sa.getHostCard();
if (sa.hasParam("AILogic")) {
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
String logic = sa.getParam("AILogic");
if (logic.equals("MomirAvatar")) {
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
return false;
}
// Set PayX here to maximum value.
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai);
// Some basic strategy for Momir
if (tokenSize < 2) {
return false;
}
if (tokenSize > 11) {
tokenSize = 11;
}
source.setSVar("PayX", Integer.toString(tokenSize));
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ai.getOpponent());
} else {
sa.getTargets().add(ai);
}
}
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
// TODO - there is no AILogic implemented yet
return false;
}
}

View File

@@ -1,8 +1,8 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
public class ChooseColorAi extends SpellAbilityAi {

View File

@@ -0,0 +1,36 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
import java.util.List;
/**
* TODO: Write javadoc for this type.
*
*/
public class ChooseGenericEffectAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/* (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) {
return canPlayAI(aiPlayer, sa);
}
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
if ("Random".equals(sa.getParam("AILogic"))) {
return Aggregates.random(spells);
} else {
return spells.get(0);
}
}
}

View File

@@ -0,0 +1,87 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.Collection;
import java.util.List;
public class ChoosePlayerAi 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 ai, SpellAbility sa) {
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> choices) {
Player chosen = null;
if ("Curse".equals(sa.getParam("AILogic"))) {
for (Player pc : choices) {
if (pc.isOpponentOf(ai)) {
chosen = pc;
break;
}
}
if (chosen == null) {
chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen);
}
} else if ("Pump".equals(sa.getParam("AILogic"))) {
chosen = choices.contains(ai) ? ai : Iterables.getFirst(choices, null);
} else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
List<Player> prefChoices = Lists.newArrayList(choices);
prefChoices.removeAll(ai.getOpponents());
if (!prefChoices.isEmpty()) {
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
}
if (chosen == null) {
chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen);
}
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
int cardsInHand = 0;
for (final Player p : choices) {
int hand = p.getCardsIn(ZoneType.Hand).size();
if (hand >= cardsInHand) {
chosen = p;
cardsInHand = hand;
}
}
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
int creats = 50;
for (final Player p : choices) {
int curr = p.getCreaturesInPlay().size();
if (curr <= creats) {
chosen = p;
creats = curr;
}
}
} else {
System.out.println("Default player choice logic.");
chosen = choices.contains(ai) ? ai : Iterables.getFirst(choices, null);
}
return chosen;
}
}

View File

@@ -0,0 +1,186 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.Collection;
import java.util.List;
public class ChooseSourceAi 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(final Player ai, SpellAbility sa) {
// TODO: AI Support! Currently this is copied from AF ChooseCard.
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
// 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
// possible reason to attack with that creature).
final Card host = sa.getHostCard();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(ai.getOpponent())) {
sa.getTargets().add(ai.getOpponent());
} else {
return false;
}
}
if (sa.hasParam("AILogic")) {
final Game game = ai.getGame();
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
if (!game.getStack().isEmpty()) {
final SpellAbility topStack = game.getStack().peekAbility();
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source)) {
return false;
}
final ApiType threatApi = topStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
return false;
}
final Card threatSource = topStack.getHostCard();
List<? extends GameObject> objects = getTargets(topStack);
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
}
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
return false;
}
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
return false;
}
return true;
}
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
return false;
}
List<Card> choices = game.getCardsIn(ZoneType.Battlefield);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
}
});
if (choices.isEmpty()) {
return false;
}
}
}
return true;
}
@Override
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
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);
if (choseCard != null) {
return choseCard;
}
}
final Combat combat = game.getCombat();
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
@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)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
}
});
return ComputerUtilCard.getBestCreatureAI(permanentSources);
} else {
return ComputerUtilCard.getBestAI(options);
}
}
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
for (SpellAbilityStackInstance si : game.getStack()) {
final Card source = si.getSourceCard();
final SpellAbility abilityOnStack = si.getSpellAbility();
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard())) {
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;
}
}

View File

@@ -0,0 +1,33 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChooseTypeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) {
return false;
}
return doTriggerAINoCost(aiPlayer, sa, false);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai);
} else {
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
if (p.isOpponentOf(ai) && !mandatory) {
return false;
}
}
}
return true;
}
}

View File

@@ -0,0 +1,36 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class ClashAi extends SpellAbilityAi {
/* (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 aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
/* (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, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getOpponent();
if (tgt != null) {
if (!sa.canTarget(opp)) {
return false;
}
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
}

View File

@@ -1,23 +1,24 @@
package forge.card.ability.ai;
package forge.ai.ability;
import java.util.List;
import forge.Card;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import java.util.List;
public class CloneAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Target tgt = sa.getTarget();
final Card source = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Game game = source.getGame();
boolean useAbility = true;
@@ -45,9 +46,7 @@ public class CloneAi extends SpellAbilityAi {
// don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if ((!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| game.getCombat().getAttackers().isEmpty())
&& !phase.isPlayerTurn(ai)) {
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
return false;
}
@@ -85,7 +84,7 @@ public class CloneAi extends SpellAbilityAi {
return false;
}
} else {
tgt.resetTargets();
sa.resetTargets();
useAbility &= cloneTgtAI(sa);
}
@@ -97,7 +96,7 @@ public class CloneAi extends SpellAbilityAi {
// AI should only activate this during Human's turn
boolean chance = true;
if (sa.getTarget() != null) {
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
}
@@ -110,7 +109,7 @@ public class CloneAi extends SpellAbilityAi {
boolean chance = true;
if (sa.getTarget() != null) {
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
}
@@ -130,9 +129,9 @@ public class CloneAi extends SpellAbilityAi {
* </p>
*
* @param af
* a {@link forge.card.ability.AbilityFactory} object.
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
private boolean cloneTgtAI(final SpellAbility sa) {
@@ -143,5 +142,14 @@ public class CloneAi extends SpellAbilityAi {
// good job of picking a good target
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)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// Didn't confirm in the original code
return false;
}
}

View File

@@ -0,0 +1,74 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Map;
public class ControlExchangeAi 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 ai, final SpellAbility sa) {
Card object1 = null;
Card object2 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list =
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard());
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
}
});
object1 = ComputerUtilCard.getBestAI(list);
if (sa.hasParam("Defined")) {
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
List<Card> list2 = ai.getCardsIn(ZoneType.Battlefield);
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard());
object2 = ComputerUtilCard.getWorstAI(list2);
sa.getTargets().add(object2);
}
if (object1 == null || object2 == null) {
return false;
}
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
sa.getTargets().add(object1);
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
return false;
}
/* (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 aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return canPlayAI(aiPlayer, sa);
}
return true;
}
}

View File

@@ -0,0 +1,228 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
//GainControl specific sa:
// LoseControl - the lose control conditions (as a comma separated list)
// -Untap - source card becomes untapped
// -LoseControl - you lose control of source card
// -LeavesPlay - source card leaves the battlefield
// -PowerGT - (not implemented yet for Old Man of the Sea)
// AddKWs - Keywords to add to the controlled card
// (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
// 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
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
/**
* <p>
* AbilityFactory_GainControl class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
*/
public class ControlGainAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
boolean hasCreature = false;
boolean hasArtifact = false;
boolean hasEnchantment = false;
boolean hasLand = false;
final List<String> lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getOpponent();
// if Defined, then don't worry about targeting
if (tgt == null) {
if (sa.hasParam("AllValid")) {
List<Card> tgtCards = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
return true;
} else {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
if (!opp.canBeTargetedBy(sa)) {
return false;
}
if (tgt.isRandomTarget()) {
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
} else {
sa.getTargets().add(opp);
}
}
}
// Don't steal something if I can't Attack without, or prevent it from
// blocking at least
if (lose != null && lose.contains("EOT")
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !sa.isTrigger()) {
return false;
}
List<Card> list =
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
// AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
if (!c.canBeTargetedBy(sa)) {
return false;
}
if (sa.isTrigger()) {
return true;
}
if (c.isCreature() && (!CombatUtil.canAttackNextTurn(c, ai.getOpponent()) || c.getNetCombatDamage() == 0)) {
return false;
}
return !vars.containsKey("RemAIDeck");
}
});
if (list.isEmpty()) {
return false;
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null;
for (final Card c : list) {
if (c.isCreature()) {
hasCreature = true;
}
if (c.isArtifact()) {
hasArtifact = true;
}
if (c.isLand()) {
hasLand = true;
}
if (c.isEnchantment()) {
hasEnchantment = true;
}
}
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
if (hasCreature) {
t = ComputerUtilCard.getBestCreatureAI(list);
} else if (hasArtifact) {
t = ComputerUtilCard.getBestArtifactAI(list);
} else if (hasLand) {
t = ComputerUtilCard.getBestLandAI(list);
} else if (hasEnchantment) {
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
} else {
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
sa.getTargets().add(t);
list.remove(t);
hasCreature = false;
hasArtifact = false;
hasLand = false;
hasEnchantment = false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
if(!this.canPlayAI(ai, sa) && mandatory) {
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
if (list.isEmpty()) {
return false;
} else {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
}
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (sa.hasParam("AllValid")) {
List<Card> tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponent());
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
final List<String> lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null;
if ((lose != null) && lose.contains("EOT")
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else {
return this.canPlayAI(ai, sa);
}
return true;
} // pumpDrawbackAI()
}

View File

@@ -0,0 +1,127 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class CopyPermanentAi 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) {
// Card source = sa.getHostCard();
// TODO - I'm sure someone can do this AI better
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
return false;
} else {
return this.doTriggerAINoCost(aiPlayer, sa, false);
}
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
// ////
// Targeting
final TargetRestrictions abTgt = sa.getTargetRestrictions();
if (abTgt != null) {
List<Card> list = aiPlayer.getGame().getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
return !vars.containsKey("RemAIDeck");
}
});
sa.resetTargets();
// target loop
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.isType("Legendary") || c.getController().isOpponentOf(aiPlayer);
}
});
Card choice;
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(list);
} else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list.remove(choice);
sa.getTargets().add(choice);
}
} 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, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
// Select a card to attach to
return ComputerUtilCard.getBestAI(options);
}
}

View File

@@ -1,16 +1,16 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.Card;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.cardfactory.CardFactoryUtil;
import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class CounterAi extends SpellAbilityAi {
@@ -19,7 +19,7 @@ public class CounterAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) {
boolean toReturn = true;
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
final Game game = ai.getGame();
if (game.getStack().isEmpty()) {
return false;
@@ -35,22 +35,22 @@ public class CounterAi extends SpellAbilityAi {
}
}
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
final SpellAbility topSA = game.getStack().peekAbility();
if (!CardFactoryUtil.isCounterableBy(topSA.getSourceCard(), sa) || topSA.getActivatingPlayer() == ai) {
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai) {
// might as well check for player's friendliness
return false;
}
if (sa.hasParam("AITgts") && (topSA.getSourceCard() == null
|| !topSA.getSourceCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source))) {
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source))) {
return false;
}
tgt.resetTargets();
sa.resetTargets();
if (sa.canTargetSpellAbility(topSA)) {
tgt.addTarget(topSA);
sa.getTargets().add(topSA);
} else {
return false;
}
@@ -108,27 +108,27 @@ public class CounterAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
final Game game = ai.getGame();
if (game.getStack().isEmpty()) {
return false;
}
final SpellAbility topSA = game.getStack().peekAbility();
if (!CardFactoryUtil.isCounterableBy(topSA.getSourceCard(), sa) || topSA.getActivatingPlayer() == ai) {
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai) {
return false;
}
tgt.resetTargets();
sa.resetTargets();
if (sa.canTargetSpellAbility(topSA)) {
tgt.addTarget(topSA);
sa.getTargets().add(topSA);
} else {
return false;
}
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
if (unlessCost != null) {
// Is this Usable Mana Sources? Or Total Available Mana?
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();

View File

@@ -15,18 +15,17 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.card.ability.ai;
import java.util.List;
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.CounterType;
import forge.game.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCard;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.util.Aggregates;
import java.util.List;
/**
* <p>
@@ -50,7 +49,7 @@ public abstract class CountersAi {
* a {@link java.lang.String} object.
* @param amount
* a int.
* @return a {@link forge.Card} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card chooseCursedTarget(final List<Card> list, final String type, final int amount) {
Card choice;
@@ -83,7 +82,7 @@ public abstract class CountersAi {
* a {@link forge.CardList} object.
* @param type
* a {@link java.lang.String} object.
* @return a {@link forge.Card} object.
* @return a {@link forge.game.card.Card} object.
*/
public static Card chooseBoonTarget(final List<Card> list, final String type) {
Card choice;

View File

@@ -1,21 +1,21 @@
package forge.card.ability.ai;
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.Card;
import forge.CardLists;
import forge.CounterType;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class CountersMoveAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
@@ -28,7 +28,7 @@ public class CountersMoveAi extends SpellAbilityAi {
// TODO handle proper calculation of X values based on Cost
int amount = 0;
if (!sa.getParam("CounterNum").equals("All")) {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// don't use it if no counters to add
if (amount <= 0) {
@@ -47,13 +47,13 @@ public class CountersMoveAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card host = sa.getSourceCard();
final Target abTgt = sa.getTarget();
final Card host = sa.getHostCard();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
int amount = 0;
if (!sa.getParam("CounterNum").equals("All")) {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
boolean chance = false;
boolean preferred = true;
@@ -124,7 +124,7 @@ public class CountersMoveAi extends SpellAbilityAi {
// TODO - I think choice can be null here. Is that ok for
// addTarget()?
abTgt.addTarget(choice);
sa.getTargets().add(choice);
}
return chance;

View File

@@ -0,0 +1,68 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
public class CountersProliferateAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
boolean chance = true;
List<Card> cperms = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
for (final CounterType c1 : CounterType.values()) {
if (crd.getCounters(c1) != 0 && !ComputerUtil.isNegativeCounter(c1, crd)) {
return true;
}
}
return false;
}
});
List<Card> hperms = CardLists.filter(ai.getOpponent().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
for (final CounterType c1 : CounterType.values()) {
if (crd.getCounters(c1) != 0 && ComputerUtil.isNegativeCounter(c1, crd)) {
return true;
}
}
return false;
}
});
if (cperms.isEmpty() && hperms.isEmpty() && ai.getOpponent().getPoisonCounters() == 0) {
return false;
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
// TODO Make sure Human has poison counters or there are some counters
// we want to proliferate
return chance;
}
/* (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 ai) {
return canPlayAI(ai, sa);
}
}

View File

@@ -0,0 +1,401 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
public class CountersPutAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on
// what the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
List<Card> list;
Card choice = null;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if ("Never".equals(sa.getParam("AILogic"))) {
return false;
}
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if (sa.hasParam("Monstrosity") && source.isMonstrous()) {
return false;
}
if (sa.hasParam("LevelUp")) {
// creatures enchanted by curse auras have low priority
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
for (Card aura : source.getEnchantedBy()) {
if (aura.getController().isOpponentOf(ai)) {
return false;
}
}
}
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
return source.getCounters(CounterType.LEVEL) < maxLevel;
}
// TODO handle proper calculation of X values based on Cost
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
}
// don't use it if no counters to add
if (amount <= 0) {
return false;
}
// Targeting
if (abTgt != null) {
sa.resetTargets();
// target loop
list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(sa) && c.canReceiveCounters(CounterType.valueOf(type));
}
});
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
if (list.size() < abTgt.getMinTargets(source, sa)) {
return false;
}
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
if (sa.isCurse()) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
} else {
choice = CountersAi.chooseBoonTarget(list, type);
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list.remove(choice);
sa.getTargets().add(choice);
if (divided) {
abTgt.addDividedAllocation(choice, amount);
break;
}
}
} else {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
// Don't activate Curse abilities on my cards and non-curse abilites
// on my opponents
if (cards.isEmpty() || !cards.get(0).getController().equals(player)) {
return false;
}
final int currCounters = cards.get(0).getCounters(CounterType.valueOf(type));
// each non +1/+1 counter on the card is a 10% chance of not
// activating this ability.
if (!(type.equals("P1P1") || type.equals("M1M1") || type.equals("ICE")) && (r.nextFloat() < (.1 * currCounters))) {
return false;
}
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use non P1P1/M1M1 counters before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")
&& !(type.equals("P1P1") || type.equals("M1M1"))
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
return true;
} // putCanPlayAI
@Override
public boolean chkAIDrawback(final SpellAbility sa, Player ai) {
boolean chance = true;
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
Card choice = null;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
if (abTgt != null) {
List<Card> list =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), abTgt.getValidTgts(), source.getController(), source);
if (list.size() == 0) {
return false;
}
sa.resetTargets();
// target loop
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return sa.canTarget(c);
}
});
if (list.size() == 0) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
break;
}
}
if (sa.isCurse()) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
} else {
choice = CountersAi.chooseBoonTarget(list, type);
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list.remove(choice);
sa.getTargets().add(choice);
if (divided) {
abTgt.addDividedAllocation(choice, amount);
break;
}
}
}
return chance;
} // putPlayDrawbackAI
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
// boolean chance = true;
boolean preferred = true;
List<Card> list;
boolean isCurse = sa.isCurse();
final Player player = isCurse ? ai.getOpponent() : ai;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final boolean divided = sa.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
if (abTgt == null) {
// No target. So must be defined
list = new ArrayList<Card>(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
if (!mandatory) {
// TODO - If Trigger isn't mandatory, when wouldn't we want to
// put a counter?
// things like Powder Keg, which are way too complex for the AI
}
} else {
list = CardLists.getTargetableCards(player.getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
if (list.isEmpty() && mandatory) {
// If there isn't any prefered cards to target, gotta choose
// non-preferred ones
list = player.getOpponent().getCardsIn(ZoneType.Battlefield);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
preferred = false;
}
// Not mandatory, or the the list was regenerated and is still
// empty,
// so return false since there are no targets
if (list.isEmpty()) {
return false;
}
Card choice = null;
// Choose targets here:
if (isCurse) {
if (preferred) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
}
else {
if (type.equals("M1M1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = Aggregates.random(list);
}
}
} else {
if (preferred) {
choice = CountersAi.chooseBoonTarget(list, type);
}
else {
if (type.equals("P1P1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = Aggregates.random(list);
}
}
if (choice != null && divided) {
abTgt.addDividedAllocation(choice, amount);
}
}
// TODO - I think choice can be null here. Is that ok for
// addTarget()?
sa.getTargets().add(choice);
}
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) {
final Card source = sa.getHostCard();
if (mode == PlayerActionConfirmMode.Tribute) {
// add counter if that opponent has a giant creature
final List<Card> creats = player.getCreaturesInPlay();
final int tributeAmount = source.getKeywordMagnitude("Tribute");
final boolean isHaste = source.hasKeyword("Haste");
List<Card> threatening = CardLists.filter(creats, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return CombatUtil.canBlock(source, c, !isHaste)
&& (c.getNetDefense() > source.getNetAttack() + tributeAmount || c.hasKeyword("DeathTouch"));
}
});
if (!threatening.isEmpty()) {
return true;
}
if (source.hasSVar("TributeAILogic")) {
final String logic = source.getSVar("TributeAILogic");
if (logic.equals("Always")) {
return true;
} else if (logic.equals("Never")) {
return false;
} else if (logic.equals("CanBlockThisTurn")) {
// pump haste
List<Card> canBlock = CardLists.filter(creats, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return CombatUtil.canBlock(source, c) && (c.getNetDefense() > source.getNetAttack() || c.hasKeyword("DeathTouch"));
}
});
if (!canBlock.isEmpty()) {
return false;
}
} else if (logic.equals("DontControlCreatures")) {
return !creats.isEmpty();
}
}
}
return MyRandom.getRandom().nextBoolean();
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(Player, SpellAbility, Collection<Player>)
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
// logic?
return Iterables.getFirst(options, null);
}
}

View File

@@ -1,26 +1,26 @@
package forge.card.ability.ai;
import java.util.List;
import java.util.Random;
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.cost.Cost;
import forge.card.spellability.AbilitySub;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class CountersPutAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
@@ -29,14 +29,14 @@ public class CountersPutAllAi extends SpellAbilityAi {
// the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
List<Card> hList;
List<Card> cList;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final String valid = sa.getParam("ValidCards");
final boolean curse = sa.isCurse();
final Target tgt = sa.getTarget();
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);
@@ -58,7 +58,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
if (tgt != null) {
Player pl = curse ? ai.getOpponent() : ai;
tgt.addTarget(pl);
sa.getTargets().add(pl);
hList = CardLists.filterControlledBy(hList, pl);
cList = CardLists.filterControlledBy(cList, pl);
@@ -72,7 +72,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
} else {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
@@ -133,4 +133,11 @@ public class CountersPutAllAi extends SpellAbilityAi {
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() >= player.getOpponent().getCreaturesInPlay().size();
}
}

View File

@@ -0,0 +1,92 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
/**
* <p>
* AbilityFactory_PutOrRemoveCountersAi class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class CountersPutOrRemoveAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
return doTriggerAINoCost(ai, sa, false);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
// if Defined, don't worry about targeting
List<ZoneType> zones = ZoneType.listValueOf(sa.getParamOrDefault("TgtZones", "Battlefield"));
List<Card> validCards = CardLists.getValidCards(ai.getGame().getCardsIn(zones),
tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
if (validCards.isEmpty()) {
return false;
}
List<Card> cWithCounters = CardLists.filter(validCards, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
return crd.hasCounters();
}
});
if (cWithCounters.isEmpty()) {
if (mandatory) {
cWithCounters = validCards;
} else {
return false;
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card targetCard = null;
if (cWithCounters.isEmpty() && ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0))) {
sa.resetTargets();
return false;
}
int random = MyRandom.getRandom().nextInt(cWithCounters.size());
targetCard = cWithCounters.get(random);
sa.getTargets().add(targetCard);
cWithCounters.remove(targetCard);
}
return true;
}
}

View File

@@ -1,15 +1,15 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.Card;
import forge.CounterType;
import forge.card.ability.SpellAbilityAi;
import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCost;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class CountersRemoveAi extends SpellAbilityAi {
@@ -19,8 +19,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
// based on what
// the expected targets could be
final Cost abCost = sa.getPayCosts();
Target abTgt = sa.getTarget();
final Card source = sa.getSourceCard();
TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
// List<Card> list;
// Card choice = null;
@@ -59,7 +59,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
// TODO handle proper calculation of X values based on Cost
// final int amount = calculateAmount(sa.getSourceCard(), amountStr, sa);
// final int amount = calculateAmount(sa.getHostCard(), amountStr, sa);
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
@@ -77,7 +77,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
if (!type.matches("Any")) {
final int currCounters = sa.getSourceCard().getCounters(CounterType.valueOf(type));
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
if (currCounters < 1) {
return false;
}

View File

@@ -1,19 +1,18 @@
package forge.card.ability.ai;
import java.util.List;
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.Card;
import forge.CardPredicates;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ai.ComputerUtilCombat;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
public abstract class DamageAiBase extends SpellAbilityAi {
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
int restDamage = d;
@@ -22,15 +21,19 @@ public abstract class DamageAiBase extends SpellAbilityAi {
if (!sa.canTarget(enemy)) {
return false;
}
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
return false;
}
// burn Planeswalkers
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
return true;
}
if (!noPrevention) {
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getSourceCard(), false);
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
} else {
restDamage = enemy.staticReplaceDamage(restDamage, sa.getSourceCard(), false);
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
}
if (restDamage == 0) {

View File

@@ -1,26 +1,21 @@
package forge.card.ability.ai;
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat;
import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DamageAllAi extends SpellAbilityAi {
@Override
@@ -29,12 +24,12 @@ public class DamageAllAi extends SpellAbilityAi {
// based on what the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
@@ -51,10 +46,10 @@ public class DamageAllAi extends SpellAbilityAi {
final List<Card> humanList = this.getKillableCreatures(sa, opp, dmg);
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(opp)) {
tgt.resetTargets();
sa.getTarget().addTarget(opp);
sa.resetTargets();
sa.getTargets().add(opp);
computerList = new ArrayList<Card>();
}
@@ -104,11 +99,11 @@ public class DamageAllAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
@@ -124,11 +119,11 @@ public class DamageAllAi extends SpellAbilityAi {
Player enemy = ai.getOpponent();
final List<Card> humanList = this.getKillableCreatures(sa, enemy, dmg);
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
tgt.resetTargets();
sa.getTarget().addTarget(enemy);
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}
// Don't get yourself killed
@@ -156,9 +151,9 @@ public class DamageAllAi extends SpellAbilityAi {
* </p>
*
* @param af
* a {@link forge.card.ability.AbilityFactory} object.
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.card.spellability.SpellAbility} object.
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @param dmg
@@ -166,7 +161,7 @@ public class DamageAllAi extends SpellAbilityAi {
* @return a {@link forge.CardList} object.
*/
private List<Card> getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
// TODO: X may be something different than X paid
@@ -188,11 +183,11 @@ public class DamageAllAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
@@ -208,11 +203,11 @@ public class DamageAllAi extends SpellAbilityAi {
Player enemy = ai.getOpponent();
final List<Card> humanList = this.getKillableCreatures(sa, enemy, dmg);
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
tgt.resetTargets();
sa.getTarget().addTarget(enemy);
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}

View File

@@ -0,0 +1,516 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.*;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DamageDealAi extends DamageAiBase {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
final Card source = sa.getHostCard();
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (!this.damageTargetAI(ai, sa, dmg)) {
return false;
}
return true;
}
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
String logic = sa.getParam("AILogic");
if ("DiscardLands".equals(logic)) {
dmg = 2;
} else if ("WildHunt".equals(logic)) {
// This dummy ability will just deal 0 damage, but holds the logic for the AI for Master of Wild Hunt
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetAttack);
}
if (dmg <= 0) {
return false;
}
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (sa.isAbility()) {
final Random r = MyRandom.getRandom(); // prevent run-away
// activations
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
return false;
}
}
if (!this.damageTargetAI(ai, sa, dmg)) {
return false;
}
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) {
// If I can kill my target by paying less mana, do it
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
for (final Card c : sa.getTargets().getTargetCards()) {
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if ((adjDamage > actualPay) && (adjDamage <= dmg)) {
actualPay = adjDamage;
}
}
source.setSVar("PayX", Integer.toString(actualPay));
}
}
return true;
}
/**
* <p>
* dealDamageChooseTgtC.
* </p>
*
* @param d
* a int.
* @param noPrevention
* a boolean.
* @param pl
* a {@link forge.game.player.Player} object.
* @param mandatory
* a boolean.
* @return a {@link forge.game.card.Card} object.
*/
private Card dealDamageChooseTgtC(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention,
final Player pl, final boolean mandatory) {
// wait until stack is empty (prevents duplicate kills)
if (!sa.isTrigger() && !ai.getGame().getStack().isEmpty()) {
return null;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
List<Card> hPlay = CardLists.getValidCards(pl.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source);
final List<GameObject> objects = Lists.newArrayList(sa.getTargets().getTargets());
if (sa.hasParam("TargetUnique")) {
objects.addAll(sa.getUniqueTargets());
}
for (final Object o : objects) {
if (o instanceof Card) {
final Card c = (Card) o;
if (hPlay.contains(c)) {
hPlay.remove(c);
}
}
}
hPlay = CardLists.getTargetableCards(hPlay, sa);
final List<Card> killables = CardLists.filter(hPlay, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) && !ComputerUtil.canRegenerate(ai, c)
&& !(c.getSVar("SacMe").length() > 0);
}
});
Card targetCard;
if (pl.isOpponentOf(ai) && !killables.isEmpty()) {
targetCard = ComputerUtilCard.getBestCreatureAI(killables);
return targetCard;
}
if (!mandatory) {
return null;
}
if (!hPlay.isEmpty()) {
if (pl.isOpponentOf(ai)) {
targetCard = ComputerUtilCard.getBestCreatureAI(hPlay);
} else {
targetCard = ComputerUtilCard.getWorstCreatureAI(hPlay);
}
return targetCard;
}
return null;
}
/**
* <p>
* damageTargetAI.
* </p>
*
* @param saMe
* a {@link forge.game.spellability.SpellAbility} object.
* @param dmg
* a int.
* @return a boolean.
*/
private boolean damageTargetAI(final Player ai, final SpellAbility saMe, final int dmg) {
final TargetRestrictions tgt = saMe.getTargetRestrictions();
if (tgt == null) {
return this.damageChooseNontargeted(ai, saMe, dmg);
}
if (tgt.isRandomTarget()) {
return false;
}
return this.damageChoosingTargets(ai, saMe, tgt, dmg, false, false);
}
/**
* <p>
* damageChoosingTargets.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param tgt
* a {@link forge.game.spellability.TargetRestrictions} object.
* @param dmg
* a int.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean damageChoosingTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, int dmg,
final boolean isTrigger, final boolean mandatory) {
final Card source = sa.getHostCard();
final boolean noPrevention = sa.hasParam("NoPrevention");
final Game game = source.getGame();
final PhaseHandler phase = game.getPhaseHandler();
final boolean divided = sa.hasParam("DividedAsYouChoose");
// target loop
sa.resetTargets();
TargetChoices tcs = sa.getTargets();
Player enemy = ai.getOpponent();
if (tgt.getMaxTargets(source, sa) <= 0) {
return false;
}
while (tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (tgt.canTgtCreatureAndPlayer()) {
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
tcs.add(enemy);
if (divided) {
tgt.addDividedAllocation(enemy, dmg);
break;
}
continue;
}
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false);
if (c != null) {
tcs.add(c);
if (divided) {
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (assignedDamage <= dmg) {
tgt.addDividedAllocation(c, assignedDamage);
}
dmg = dmg - assignedDamage;
if (dmg <= 0) {
break;
}
}
continue;
}
// When giving priority to targeting Creatures for mandatory
// triggers
// feel free to add the Human after we run out of good targets
// TODO: add check here if card is about to die from something
// on the stack
// or from taking combat damage
boolean freePing = isTrigger || sa.getPayCosts() == null || sa.getTargets().getNumTargeted() > 0;
if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility()) {
if (phase.getNextTurn().equals(ai))
freePing = true;
}
if (phase.is(PhaseType.MAIN2) && sa.isAbility()) {
if (sa.getRestrictions().getPlaneswalker() || source.hasKeyword("At the beginning of the end step, exile CARDNAME.")
|| source.hasKeyword("At the beginning of the end step, sacrifice CARDNAME."))
freePing = true;
}
if (freePing && sa.canTarget(enemy)) {
tcs.add(enemy);
if (divided) {
tgt.addDividedAllocation(enemy, dmg);
break;
}
}
} else if (tgt.canTgtCreature()) {
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, mandatory);
if (c != null) {
tcs.add(c);
if (divided) {
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (assignedDamage <= dmg) {
tgt.addDividedAllocation(c, assignedDamage);
} else {
tgt.addDividedAllocation(c, dmg);
}
dmg = dmg - assignedDamage;
if (dmg <= 0) {
break;
}
}
continue;
}
}
// TODO: Improve Damage, we shouldn't just target the player just
// because we can
else if (sa.canTarget(enemy)) {
if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|| sa.getPayCosts() == null || isTrigger
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) {
sa.getTargets().add(enemy);
if (divided) {
tgt.addDividedAllocation(enemy, dmg);
break;
}
continue;
}
}
// fell through all the choices, no targets left?
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa) || sa.getTargets().getNumTargeted() == 0) {
if (!mandatory) {
sa.resetTargets();
return false;
} else {
// If the trigger is mandatory, gotta choose my own stuff now
return this.damageChooseRequiredTargets(ai, sa, tgt, dmg, mandatory);
}
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
return true;
}
/**
* <p>
* damageChooseNontargeted.
* </p>
* @param ai
*
* @param saMe
* a {@link forge.game.spellability.SpellAbility} object.
* @param dmg
* a int.
* @return a boolean.
*/
private boolean damageChooseNontargeted(Player ai, final SpellAbility saMe, final int dmg) {
// TODO: Improve circumstances where the Defined Damage is unwanted
final List<GameObject> objects = AbilityUtils.getDefinedObjects(saMe.getHostCard(), saMe.getParam("Defined"), saMe);
boolean urgent = false; // can it wait?
boolean positive = false;
for (final Object o : objects) {
if (o instanceof Card) {
Card c = (Card) o;
final int restDamage = ComputerUtilCombat.predictDamageTo(c, dmg, saMe.getHostCard(), false);
if (!c.hasKeyword("Indestructible") && ComputerUtilCombat.getDamageToKill(c) <= restDamage) {
if (c.getController().equals(ai)) {
return false;
} else {
urgent = true;
}
}
if (c.getController().isOpponentOf(ai) ^ c.getName().equals("Stuffy Doll")) {
positive = true;
}
} else if (o instanceof Player) {
final Player p = (Player) o;
final int restDamage = ComputerUtilCombat.predictDamageTo(p, dmg, saMe.getHostCard(), false);
if (!p.isOpponentOf(ai) && p.canLoseLife() && restDamage + 3 >= p.getLife() && restDamage > 0) {
// from this spell will kill me
return false;
}
if (p.isOpponentOf(ai) && p.canLoseLife()) {
positive = true;
if (p.getLife() + 3 <= restDamage) {
urgent = true;
}
}
}
}
if (!positive && !(saMe instanceof AbilitySub)) {
return false;
}
if (!urgent && !SpellAbilityAi.playReusable(ai, saMe)) {
return false;
}
return true;
}
/**
* <p>
* damageChooseRequiredTargets.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param tgt
* a {@link forge.game.spellability.TargetRestrictions} object.
* @param dmg
* a int.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg,
final boolean mandatory) {
// this is for Triggered targets that are mandatory
final boolean noPrevention = sa.hasParam("NoPrevention");
final boolean divided = sa.hasParam("DividedAsYouChoose");
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
// TODO: Consider targeting the planeswalker
if (tgt.canTgtCreature()) {
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory);
if (c != null) {
sa.getTargets().add(c);
if (divided) {
tgt.addDividedAllocation(c, dmg);
break;
}
continue;
}
}
if (sa.canTarget(ai)) {
if (sa.getTargets().add(ai)) {
if (divided) {
tgt.addDividedAllocation(ai, dmg);
break;
}
continue;
}
}
// if we get here then there isn't enough targets, this is the only
// time we can return false
return false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
// Remove all damage
if (sa.hasParam("Remove")) {
return true;
}
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// If it's not mandatory check a few things
if (!mandatory && !this.damageChooseNontargeted(ai, sa, dmg)) {
return false;
}
} else {
if (!this.damageChoosingTargets(ai, sa, tgt, dmg, true, mandatory) && !mandatory) {
return false;
}
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
// If I can kill my target by paying less mana, do it
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
//target is a player
if (!sa.getTargets().isTargetingAnyCard()) {
actualPay = dmg;
}
for (final Card c : sa.getTargets().getTargetCards()) {
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (adjDamage > actualPay) {
actualPay = adjDamage;
}
}
source.setSVar("PayX", Integer.toString(actualPay));
}
}
return true;
}
}

View File

@@ -0,0 +1,43 @@
package forge.ai.ability;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class DamageEachAi extends DamageAiBase {
/* (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, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(ai.getOpponent())) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
}
final String damage = sa.getParam("NumDmg");
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
return this.shouldTgtP(ai, sa, iDmg, false);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// check AI life before playing this drawback?
return true;
}
/* (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) {
return canPlayAI(ai, sa);
}
}

View File

@@ -0,0 +1,224 @@
package forge.ai.ability;
import forge.ai.*;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.List;
public class DamagePreventAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard();
final Game game = ai.getGame();
final Combat combat = game.getCombat();
boolean chance = false;
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// As far as I can tell these Defined Cards will only have one of
// them
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
// react to threats on the stack
if (!game.getStack().isEmpty()) {
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Object o : objects) {
if (threatenedObjects.contains(o)) {
chance = true;
}
}
} else {
PhaseHandler handler = game.getPhaseHandler();
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
boolean flag = false;
for (final Object o : objects) {
if (o instanceof Card) {
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
} else if (o instanceof Player) {
// Don't need to worry about Combat Damage during AI's turn
final Player p = (Player) o;
if (!handler.isPlayerTurn(p)) {
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
}
}
}
chance = flag;
} else { // if nothing on the stack, and it's not declare
// blockers. no need to prevent
return false;
}
}
} // non-targeted
// react to threats on the stack
else if (!game.getStack().isEmpty()) {
sa.resetTargets();
final TargetChoices tcs = sa.getTargets();
// check stack for something on the stack will kill anything i control
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
if (objects.contains(ai)) {
tcs.add(ai);
chance = true;
}
final List<Card> threatenedTargets = new ArrayList<Card>();
// filter AIs battlefield by what I can target
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard);
targetables = CardLists.getTargetableCards(targetables, sa);
for (final Card c : targetables) {
if (objects.contains(c)) {
threatenedTargets.add(c);
}
}
if (!threatenedTargets.isEmpty()) {
// Choose "best" of the remaining to save
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
chance = true;
}
} // Protect combatants
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
sa.resetTargets();
final TargetChoices tcs = sa.getTargets();
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
tcs.add(ai);
chance = true;
} else {
// filter AIs battlefield by what I can target
List<Card> targetables = ai.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard);
targetables = CardLists.getTargetableCards(targetables, sa);
if (targetables.isEmpty()) {
return false;
}
final List<Card> combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
ComputerUtilCard.sortByEvaluateCreature(combatants);
for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
tcs.add(c);
chance = true;
}
}
}
}
if (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));
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
boolean chance = false;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// If there's no target on the trigger, just say yes.
chance = true;
} else {
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
}
return chance;
}
/**
* <p>
* preventDamageMandatoryTarget.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
// filter AIs battlefield by what I can target
final Game game = ai.getGame();
List<Card> targetables = game.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard());
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
Card target = null;
if (targetables.isEmpty()) {
return false;
}
if (!mandatory && compTargetables.isEmpty()) {
return false;
}
if (!compTargetables.isEmpty()) {
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
ComputerUtilCard.sortByEvaluateCreature(combatants);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat();
for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
target = c;
break;
}
}
}
if (target == null) {
target = combatants.get(0);
}
} else {
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
}
sa.getTargets().add(target);
if (sa.hasParam("DividedAsYouChoose")) {
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
}
return true;
}
}

View File

@@ -1,13 +1,13 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.Card;
import forge.card.ability.SpellAbilityAi;
import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility;
import forge.game.ai.ComputerUtilCost;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class DamagePreventAllAi extends SpellAbilityAi {
@@ -16,7 +16,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getSourceCard();
final Card hostCard = sa.getHostCard();
boolean chance = false;
final Cost cost = sa.getPayCosts();

View File

@@ -0,0 +1,298 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityRestriction;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DebuffAi extends SpellAbilityAi {
// *************************************************************************
// ***************************** Debuff ************************************
// *************************************************************************
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
// if there is no target and host card isn't in play, don't activate
final Card source = sa.getHostCard();
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
return false;
}
final Cost cost = sa.getPayCosts();
// temporarily disabled until AI is improved
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, null)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
return false;
}
final SpellAbilityRestriction restrict = sa.getRestrictions();
final PhaseHandler ph = game.getPhaseHandler();
// Phase Restrictions
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getStack().isEmpty()) {
// Instant-speed pumps should not be cast outside of combat when the
// stack is empty
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
}
final int activations = restrict.getNumberTurnActivations();
final int sacActivations = restrict.getActivationNumberSacrifice();
// don't risk sacrificing a creature just to pump it
if ((sacActivations != -1) && (activations >= (sacActivations - 1))) {
return false;
}
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>() {
@Override
public boolean apply(final Card c) {
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
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(" & ")));
}
});
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
// here?
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
return true;
} // debuffDrawbackAI()
/**
* <p>
* debuffTgtAI.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param kws
* a {@link java.util.ArrayList} object.
* @param mandatory
* 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
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
// several uses here:
// 1. make human creatures lose evasion when they are attacking
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
// Comp is attacking
// 3. remove Indestructible keyword so it can be destroyed?
// 3a. remove Persist?
if (list.isEmpty()) {
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
if (mandatory) {
return debuffMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
t = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(t);
list.remove(t);
}
return true;
} // pumpTgtAI()
/**
* <p>
* getCurseCreatures.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param kws
* a {@link java.util.ArrayList} object.
* @return a {@link forge.CardList} object.
*/
private List<Card> getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ai.getOpponent();
List<Card> list = opp.getCreaturesInPlay();
list = CardLists.getTargetableCards(list, sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasAnyKeyword(kws); // don't add duplicate negative
// keywords
}
});
}
return list;
} // getCurseCreatures()
/**
* <p>
* debuffMandatoryTarget.
* </p>
*
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
List<Card> list = ai.getGame().getCardsIn(ZoneType.Battlefield);
final TargetRestrictions tgt = sa.getTargetRestrictions();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) {
list.remove(c);
}
final List<Card> pref = CardLists.filterControlledBy(list, ai.getOpponent());
final List<Card> forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (forced.isEmpty()) {
break;
}
// TODO - if forced targeting, just pick something without the given
// keyword
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
return true;
} // pumpMandatoryTarget()
@Override
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) {
return true;
}
} else {
return debuffTgtAI(ai, sa, kws, mandatory);
}
return true;
}
}

View File

@@ -1,27 +1,26 @@
package forge.card.ability.ai;
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.game.phase.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DebuffAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
String valid = "";
final Random r = MyRandom.getRandom();
// final Card source = sa.getSourceCard();
final Card hostCard = sa.getSourceCard();
// final Card source = sa.getHostCard();
final Card hostCard = sa.getHostCard();
final Player opp = ai.getOpponent();
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); // to

View File

@@ -0,0 +1,50 @@
package forge.ai.ability;
import forge.ai.AiController;
import forge.ai.AiPlayDecision;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.ability.AbilityFactory;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
public class DelayedTriggerAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final String svarName = sa.getParam("Execute");
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
trigsa.setActivatingPlayer(ai);
if (trigsa instanceof AbilitySub) {
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
} else {
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}
@Override
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();
trigsa.setActivatingPlayer(ai);
if (!sa.hasParam("OptionalDecider")) {
return aic.doTrigger(trigsa, true);
} else {
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
}
}
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final String svarName = sa.getParam("Execute");
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}

View File

@@ -0,0 +1,266 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostSacrifice;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class DestroyAi extends SpellAbilityAi {
/* (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 ai) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final boolean noRegen = sa.hasParam("NoRegen");
List<Card> list;
if (abCost != null) {
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// Targeting
if (abTgt != null) {
sa.resetTargets();
list = CardLists.getTargetableCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
if (sa.hasParam("AITgts")) {
list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
}
list = CardLists.getNotKeyword(list, "Indestructible");
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
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
return false;
}
}
}
}
//Check for undying
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
}
});
}
// If NoRegen is not set, filter out creatures that have a
// regeneration shield
if (!noRegen) {
// TODO filter out things that might be tougher?
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.getShield().isEmpty() && !ComputerUtil.canRegenerate(ai, c));
}
});
}
if (list.size() == 0) {
return false;
}
// target loop
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
if (list.size() == 0) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
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()) {
choice = ComputerUtilCard.getBestLandAI(list);
} else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
} else {
// Don't destroy stolen permanents when the stealing aura can be destroyed
if (choice.getOwner() == ai) {
for (Card aura : choice.getEnchantedBy()) {
SpellAbility sp = aura.getFirstSpellAbility();
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
&& aura.getController() != ai && sa.canTarget(aura)) {
choice = aura;
}
}
}
}
list.remove(choice);
sa.getTargets().add(choice);
}
} else {
if (sa.hasParam("Defined")) {
list = new ArrayList<Card>(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
if (list.isEmpty()
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
return false;
}
}
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final boolean noRegen = sa.hasParam("NoRegen");
if (tgt != null) {
List<Card> list;
list = ai.getGame().getCardsIn(ZoneType.Battlefield);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source);
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false;
}
sa.resetTargets();
List<Card> preferred = CardLists.getNotKeyword(list, "Indestructible");
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
// If NoRegen is not set, filter out creatures that have a
// regeneration shield
if (!noRegen) {
// TODO filter out things that could regenerate in response?
// might be tougher?
preferred = CardLists.filter(preferred, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getShield().isEmpty();
}
});
}
for (final Card c : preferred) {
list.remove(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
if (preferred.isEmpty()) {
if ((sa.getTargets().getNumTargeted() == 0)
|| (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa))) {
if (!mandatory) {
sa.resetTargets();
return false;
} else {
break;
}
} else {
break;
}
} else {
Card c;
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
c = ComputerUtilCard.getBestCreatureAI(preferred);
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
c = ComputerUtilCard.getBestLandAI(preferred);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
}
sa.getTargets().add(c);
preferred.remove(c);
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (list.isEmpty()) {
break;
} else {
Card c;
if (CardLists.getNotType(list, "Creature").isEmpty()) {
c = ComputerUtilCard.getWorstCreatureAI(list);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
}
sa.getTargets().add(c);
list.remove(c);
}
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false;
}
} else {
if (!mandatory) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,143 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
public class DestroyAllAi extends SpellAbilityAi {
private static final Predicate<Card> predicate = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !(c.hasKeyword("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)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
String valid = "";
if (mandatory) {
return true;
}
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
List<Card> humanlist = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
List<Card> computerlist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
computerlist.clear();
}
humanlist = CardLists.filter(humanlist, predicate);
computerlist = CardLists.filter(computerlist, predicate);
if (humanlist.isEmpty() && !computerlist.isEmpty()) {
return false;
}
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
if (ComputerUtilCard.evaluateCreatureList(computerlist) >= ComputerUtilCard.evaluateCreatureList(humanlist)
&& !computerlist.isEmpty()) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if (ComputerUtilCard.evaluatePermanentList(computerlist) >= ComputerUtilCard.evaluatePermanentList(humanlist)) {
return false;
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
//TODO: Check for bad outcome
return true;
}
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
String valid = "";
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
valid = valid.replace("X", Integer.toString(xPay));
}
List<Card> humanlist = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
List<Card> computerlist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
computerlist.clear();
}
humanlist = CardLists.filter(humanlist, predicate);
computerlist = CardLists.filter(computerlist, predicate);
if (abCost != null) {
// AI currently disabled for some costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if (CardLists.getNotType(humanlist, "Creature").isEmpty() && CardLists.getNotType(computerlist, "Creature").isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(computerlist) + 200 >= ComputerUtilCard.evaluateCreatureList(humanlist)) {
return false;
}
} // only lands involved
else if (CardLists.getNotType(humanlist, "Land").isEmpty() && CardLists.getNotType(computerlist, "Land").isEmpty()) {
if (ai.isCardInPlay("Crucible of Worlds") && !ai.getOpponent().isCardInPlay("Crucible of Worlds") && !humanlist.isEmpty()) {
return true;
}
if (ComputerUtilCard.evaluatePermanentList(computerlist) + 1 >= ComputerUtilCard.evaluatePermanentList(humanlist)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
.evaluatePermanentList(humanlist)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,102 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.Collection;
import java.util.Random;
public class DigAi 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 ai, SpellAbility sa) {
Player opp = ai.getOpponent();
final Card host = sa.getHostCard();
Player libraryOwner = ai;
if (sa.usesTargeting()) {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
// don't deck yourself
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 + 1) {
return false;
}
}
// Don't use draw abilities before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
}
return true;
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> valid, boolean isOptional, Player relatedPlayer) {
Card chosen = ComputerUtilCard.getBestAI(valid);
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
return ComputerUtilCard.getWorstAI(valid);
}
return chosen;
}
/* (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) {
// looks like perfect code for Delver of Secrets, but what about other cards?
Card topc = player.getZone(ZoneType.Library).get(0);
return topc.isInstant() || topc.isSorcery();
}
}

View File

@@ -0,0 +1,117 @@
package forge.ai.ability;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DigUntilAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Card source = sa.getHostCard();
double chance = .4; // 40 percent chance with instant speed stuff
if (SpellAbilityAi.isSorcerySpeed(sa)) {
chance = .667; // 66.7% chance for sorcery speed (since it will
// never activate EOT)
}
final Random r = MyRandom.getRandom();
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
Player libraryOwner = ai;
Player opp = ai.getOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (!sa.canTarget(opp)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
} else {
if (sa.hasParam("Valid")) {
final String valid = sa.getParam("Valid");
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source).isEmpty()) {
return false;
}
}
}
final String num = sa.getParam("Amount");
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("")) {
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (numCards <= 0) {
return false;
}
source.setSVar("PayX", Integer.toString(numCards));
}
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.isCurse()) {
for (Player opp : ai.getOpponents()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
break;
}
}
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
} else {
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,29 +1,29 @@
package forge.card.ability.ai;
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.Card;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DiscardAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Target tgt = sa.getTarget();
final Card source = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Cost abCost = sa.getPayCosts();
if (abCost != null) {
@@ -54,7 +54,7 @@ public class DiscardAi extends SpellAbilityAi {
}
} else {
// TODO: Add appropriate restrictions
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getSourceCard(),
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(),
sa.getParam("Defined"), sa);
if (players.size() == 1) {
@@ -85,7 +85,7 @@ public class DiscardAi extends SpellAbilityAi {
}
source.setSVar("PayX", Integer.toString(cardsToDiscard));
} else {
if (AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) < 1) {
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
return false;
}
}
@@ -113,14 +113,14 @@ public class DiscardAi extends SpellAbilityAi {
} // discardCanPlayAI()
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getOpponent();
if (opp.getCardsIn(ZoneType.Hand).isEmpty()) {
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
if (tgt != null) {
if (sa.canTarget(opp)) {
tgt.addTarget(opp);
sa.getTargets().add(opp);
return true;
}
}
@@ -131,24 +131,24 @@ public class DiscardAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
Player opp = ai.getOpponent();
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) {
tgt.addTarget(opp);
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
tgt.addTarget(ai);
sa.getTargets().add(ai);
} else {
return false;
}
}
} else {
if ("X".equals(sa.getParam("RevealNumber")) && sa.getSourceCard().getSVar("X").equals("Count$xPaid")) {
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()
.getCardsIn(ZoneType.Hand).size());
sa.getSourceCard().setSVar("PayX", Integer.toString(cardsToDiscard));
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
}
}
@@ -159,7 +159,7 @@ public class DiscardAi extends SpellAbilityAi {
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// Drawback AI improvements
// if parent draws cards, make sure cards in hand + cards drawn > 0
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
return discardTargetAI(ai, sa);
}

View File

@@ -0,0 +1,91 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DrainManaAi extends SpellAbilityAi {
@Override
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 Player opp = ai.getOpponent();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (tgt == null) {
// assume we are looking to tap human's stuff
// TODO - check for things with untap abilities, and don't tap
// those.
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
if (null == tgt) {
if (mandatory) {
return true;
} else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
}
return true;
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
boolean randomReturn = true;
if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (defined.contains(ai)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
}
return randomReturn;
}
}

View File

@@ -16,30 +16,23 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.card.ability.ai;
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import forge.Card;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.cost.Cost;
import forge.card.cost.CostDiscard;
import forge.card.cost.CostPart;
import forge.card.cost.PaymentDecision;
import forge.card.spellability.AbilitySub;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.ai.*;
import forge.game.Game;
import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
import forge.game.cost.PaymentDecision;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class DrawAi extends SpellAbilityAi {
@@ -53,8 +46,8 @@ public class DrawAi extends SpellAbilityAi {
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Target tgt = sa.getTarget();
final Card source = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Cost abCost = sa.getPayCosts();
final Game game = ai.getGame();
@@ -69,10 +62,10 @@ public class DrawAi extends SpellAbilityAi {
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
AiCostDecision aiDecisions = new AiCostDecision(ai, sa);
for (final CostPart part : abCost.getCostParts()) {
if (part instanceof CostDiscard) {
CostDiscard cd = (CostDiscard) part;
PaymentDecision decision = cd.decideAIPayment(ai, sa, sa.getSourceCard());
PaymentDecision decision = part.accept(aiDecisions);
if ( null == decision )
return false;
for (Card discard : decision.cards) {
@@ -95,12 +88,25 @@ public class DrawAi extends SpellAbilityAi {
}
if (tgt != null) {
final List<Player> players = tgt.getTargetPlayers();
if ((players.size() > 0) && players.get(0).isOpponentOf(ai)) {
final Player player = sa.getTargets().getFirstTargetedPlayer();
if (player != null && player.isOpponentOf(ai)) {
return true;
}
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
return false;
}
// Don't use draw abilities before main 2 if possible
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
@@ -110,7 +116,7 @@ public class DrawAi extends SpellAbilityAi {
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
&& ai.getCardsIn(ZoneType.Hand).size() > 1
&& !ComputerUtil.ActivateForSacCost(sa, ai)) {
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
@@ -119,28 +125,12 @@ public class DrawAi extends SpellAbilityAi {
return false;
}
double chance = .4; // 40 percent chance of drawing with instant speed
// stuff
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
if (SpellAbilityAi.isSorcerySpeed(sa)) {
randomReturn = true;
}
if ((game.getPhaseHandler().is(PhaseType.END_OF_TURN)
&& game.getPhaseHandler().getNextTurn().equals(ai))) {
randomReturn = true;
}
if (SpellAbilityAi.playReusable(ai, sa)) {
randomReturn = true;
}
return randomReturn;
return true;
}
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Target tgt = sa.getTarget();
final Card source = sa.getSourceCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final boolean drawback = (sa instanceof AbilitySub);
final Game game = ai.getGame();
Player opp = ai.getOpponent();
@@ -157,7 +147,7 @@ public class DrawAi extends SpellAbilityAi {
int numCards = 1;
if (sa.hasParam("NumCards")) {
numCards = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa);
numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
}
boolean xPaid = false;
@@ -175,12 +165,11 @@ public class DrawAi extends SpellAbilityAi {
//if (n)
// TODO: if xPaid and one of the below reasons would fail, instead of
// bailing
// reduce toPay amount to acceptable level
// bailing reduce toPay amount to acceptable level
if (tgt != null) {
// ability is targeted
tgt.resetTargets();
sa.resetTargets();
final boolean canTgtHuman = sa.canTarget(opp);
final boolean canTgtComp = sa.canTarget(ai);
@@ -192,7 +181,7 @@ public class DrawAi extends SpellAbilityAi {
if (canTgtHuman && !opp.cantLose() && numCards >= humanLibrarySize) {
// Deck the Human? DO IT!
tgt.addTarget(opp);
sa.getTargets().add(opp);
return true;
}
@@ -228,9 +217,9 @@ public class DrawAi extends SpellAbilityAi {
}
if ((!tgtHuman || !canTgtHuman) && canTgtComp) {
tgt.addTarget(ai);
sa.getTargets().add(ai);
} else if (mandatory && canTgtHuman) {
tgt.addTarget(opp);
sa.getTargets().add(opp);
} else {
return false;
}
@@ -275,7 +264,7 @@ public class DrawAi extends SpellAbilityAi {
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) : 1;
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
// AI shouldn't mill itself
return numCards < player.getZone(ZoneType.Library).size();
}

View File

@@ -0,0 +1,163 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class EffectAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= .6667;
final Player opp = ai.getOpponent();
String logic = "";
if (sa.hasParam("AILogic")) {
logic = sa.getParam("AILogic");
final PhaseHandler phase = game.getPhaseHandler();
if (logic.equals("BeginningOfOppTurn")) {
if (phase.isPlayerTurn(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
return false;
}
randomReturn = true;
} else if (logic.equals("EndOfOppTurn")) {
if (phase.isPlayerTurn(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
randomReturn = true;
} else if (logic.equals("Fog")) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;
}
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
if (!game.getStack().isEmpty()) {
return false;
}
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
List<Card> list = game.getCombat().getAttackers();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
list = CardLists.getTargetableCards(list, sa);
Card target = ComputerUtilCard.getBestCreatureAI(list);
if (target == null) {
return false;
}
sa.getTargets().add(target);
}
randomReturn = true;
} else if (logic.equals("Always")) {
randomReturn = true;
} else if (logic.equals("Main2")) {
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
randomReturn = true;
} else if (logic.equals("Evasion")) {
List<Card> comp = ai.getCreaturesInPlay();
List<Card> human = opp.getCreaturesInPlay();
// only count creatures that can attack or block
comp = CardLists.filter(comp, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, opp);
}
});
human = CardLists.filter(human, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canBlock(c);
}
});
if (comp.size() < 2 || human.size() < 1) {
randomReturn = false;
}
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
if (game.getStack().isEmpty()) {
return false;
}
boolean threatened = false;
for (final SpellAbilityStackInstance stackSA : game.getStack()) {
if (!stackSA.isSpell()) { continue; }
if (stackSA.getSpellAbility().getApi() == ApiType.DealDamage) {
final SpellAbility saTargeting = stackSA.getSpellAbility().getSATargetingPlayer();
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
threatened = true;
}
}
}
randomReturn = threatened;
}
} else { //no AILogic
return false;
}
if ("False".equals(sa.getParam("Stackable"))) {
String name = sa.getParam("Name");
if (name == null) {
name = sa.getHostCard().getName() + "'s Effect";
}
final List<Card> list = sa.getActivatingPlayer().getCardsIn(ZoneType.Command, name);
if (!list.isEmpty()) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && tgt.canTgtPlayer()) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
sa.getTargets().add(ai.getOpponent());
} else {
sa.getTargets().add(ai);
}
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Player opp = aiPlayer.getOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(aiPlayer)) {
sa.getTargets().add(aiPlayer);
}
}
return true;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import java.util.Collection;
import java.util.List;
/**
* <p>
* AbilityFactoryBond class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/
public final class EncodeAi extends SpellAbilityAi {
/**
* <p>
* bondCanPlayAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return true;
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
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(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
Card choice = null;
// final String logic = sa.getParam("AILogic");
// if (logic == null) {
final List<Card> attackers = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttackNextTurn(c);
}
});
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !CombatUtil.canBeBlocked(c, ai.getOpponent());
}
});
if (!unblockables.isEmpty()) {
choice = ComputerUtilCard.getBestAI(unblockables);
} else if (!attackers.isEmpty()) {
choice = ComputerUtilCard.getBestAI(attackers);
} else {
choice = ComputerUtilCard.getBestAI(options);
}
// }
return choice;
}
}

View File

@@ -1,9 +1,9 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.

View File

@@ -0,0 +1,139 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class FightAi 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 ai, SpellAbility sa) {
sa.resetTargets();
final Card source = sa.getHostCard();
List<Card> aiCreatures = ai.getCreaturesInPlay();
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
final Random r = MyRandom.getRandom();
if (r.nextFloat() > Math.pow(.6667, sa.getActivationsThisTurn())) {
return false;
}
//assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetAttack()
&& humanCreature.getNetAttack() < ComputerUtilCombat.getDamageToKill(fighter1)) {
// todo: check min/max targets; see if we picked the best matchup
sa.getTargets().add(humanCreature);
return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
sa.getTargets().add(humanCreature);
return true;
}
}
}
if (sa.hasParam("TargetsFromDifferentZone")) {
if (humCreatures.isEmpty() && aiCreatures.isEmpty()) {
for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack()
&& humanCreature.getNetAttack() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
// todo: check min/max targets; see if we picked the best matchup
sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature);
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) {
if (creature1.equals(creature2)) {
continue;
}
if (sa.hasParam("TargetsWithoutSameCreatureType")
&& creature1.sharesCreatureTypeWith(creature2)) {
continue;
}
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetAttack()
&& creature1.getNetAttack() >= ComputerUtilCombat.getDamageToKill(creature2)) {
// todo: check min/max targets; see if we picked the best matchup
sa.getTargets().add(creature1);
sa.getTargets().add(creature2);
return true;
}
}
}
return false;
}
/* (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) {
if (canPlayAI(ai, sa)) {
return true;
}
if (!mandatory) {
return false;
}
//try to make a good trade or no trade
final Card source = sa.getHostCard();
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
if (humCreatures.isEmpty()) {
return false;
}
//assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack()
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
sa.getTargets().add(humanCreature);
return true;
}
}
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetAttack()) {
sa.getTargets().add(humanCreature);
return true;
}
}
sa.getTargets().add(humCreatures.get(0));
return true;
}
return true;
}
}

View File

@@ -1,8 +1,8 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class FlipACoinAi extends SpellAbilityAi {

View File

@@ -1,12 +1,12 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ai.ComputerUtilCombat;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class FogAi extends SpellAbilityAi {

View File

@@ -0,0 +1,49 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class GameLossAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ai.getOpponent();
if (opp.cantLose()) {
return false;
}
// Only one SA Lose the Game card right now, which is Door to
// Nothingness
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
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
return true;
}
@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
// specific turn, which can't be done yet)
if (!mandatory && ai.getOpponent().cantLose()) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(ai.getOpponent());
}
return true;
}
}

View File

@@ -1,9 +1,9 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class GameWinAi extends SpellAbilityAi {
/* (non-Javadoc)

View File

@@ -0,0 +1,30 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.Collection;
import java.util.List;
public class HauntAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> creats, boolean isOptional, Player targetedPlayer) {
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
return ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats);
}
}

View File

@@ -0,0 +1,41 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.Collection;
/**
* TODO: Write javadoc for this type.
*
*/
public class LegendaryRuleAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
// Choose a single legendary/planeswalker card to keep
Card firstOption = Iterables.getFirst(options, null);
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
if ( choosingFromPlanewalkers ) {
// AI decision making - should AI compare counters?
} else {
// AI decision making - should AI compare damage and debuffs?
}
return firstOption;
}
}

View File

@@ -1,13 +1,13 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.Random;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.player.Player;
import forge.util.MyRandom;
public class LifeExchangeAi extends SpellAbilityAi {
/*
@@ -37,12 +37,12 @@ public class LifeExchangeAi extends SpellAbilityAi {
* and one card that has a conditional (Psychic Transfer) that are
* not currently handled
*/
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
tgt.resetTargets();
sa.resetTargets();
if (opponent.canBeTargetedBy(sa)) {
// never target self, that would be silly for exchange
tgt.addTarget(opponent);
sa.getTargets().add(opponent);
if (!opponent.canLoseLife()) {
return false;
}

View File

@@ -0,0 +1,161 @@
package forge.ai.ability;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class LifeGainAi 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 Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final Game game = source.getGame();
final int life = ai.getLife();
final String amountStr = sa.getParam("LifeAmount");
int lifeAmount = 0;
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
lifeAmount = xPay;
} else {
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// don't use it if no life to gain
if (!activateForCost && lifeAmount <= 0) {
return false;
}
// don't play if the conditions aren't met, unless it would trigger a
// beneficial sub-condition
if (!activateForCost && !sa.getConditions().areMet(sa)) {
final AbilitySub abSub = sa.getSubAbility();
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
if (!abSub.getConditions().areMet(abSub)) {
return false;
}
} else {
return false;
}
}
boolean lifeCritical = life <= 5;
lifeCritical |= game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
if (abCost != null && !lifeCritical) {
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, false)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if (!activateForCost && !ai.canGainLife()) {
return false;
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use lifegain before main 2 if possible
if (!lifeCritical && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if (!lifeCritical && !activateForCost && (!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
return false;
}
}
return true;
}
/**
* <p>
* gainLifeDoTriggerAINoCost.
* </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) {
// If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to be
// handled better
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else if (mandatory && sa.canTarget(ai.getOpponent())) {
sa.getTargets().add(ai.getOpponent());
} else {
return false;
}
}
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
}
return true;
}
}

View File

@@ -0,0 +1,170 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import java.util.List;
public class LifeLoseAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
List<Player> tgtPlayers = getTargetPlayers(sa);
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
return false;
}
return true;
}
/* (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 Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
// TODO handle proper calculation of X values based on Cost and what
// would be paid
int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
}
if (amount <= 0) {
return false;
}
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, amount, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
Player opp = ai.getOpponent();
if (!opp.canLoseLife()) {
return false;
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
return false;
}
}
if (amount >= opp.getLife()) {
return true; // killing the human should be done asap
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use loselife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (SpellAbilityAi.isSorcerySpeed(sa)
|| sa.hasParam("ActivationPhases")
|| SpellAbilityAi.playReusable(ai, sa)
|| ComputerUtil.activateForCost(sa, ai)) {
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
if (sa.canTarget(ai.getOpponent())) {
sa.getTargets().add(ai.getOpponent());
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
return false;
}
}
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
List<Player> tgtPlayers = getTargetPlayers(sa);
if (!mandatory && tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
// For cards like Foul Imp, ETB you lose life
return false;
}
return true;
}
}

View File

@@ -1,25 +1,25 @@
package forge.card.ability.ai;
package forge.ai.ability;
import java.util.Random;
import forge.Card;
import forge.CounterType;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.ai.ComputerUtilMana;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.Random;
public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Random r = MyRandom.getRandom();
// Ability_Cost abCost = sa.getPayCosts();
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
final int myLife = ai.getLife();
final Player opponent = ai.getOpponent();
final int hlife = opponent.getLife();
@@ -45,17 +45,17 @@ public class LifeSetAi extends SpellAbilityAi {
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
tgt.resetTargets();
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
tgt.addTarget(opponent);
sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life
// would
// go up, don't play it.
@@ -67,11 +67,11 @@ public class LifeSetAi extends SpellAbilityAi {
}
} else {
if ((amount > myLife) && (myLife <= 10)) {
tgt.addTarget(ai);
sa.getTargets().add(ai);
} else if (hlife > amount) {
tgt.addTarget(opponent);
sa.getTargets().add(opponent);
} else if (amount > myLife) {
tgt.addTarget(ai);
sa.getTargets().add(ai);
} else {
return false;
}
@@ -105,7 +105,7 @@ public class LifeSetAi extends SpellAbilityAi {
final int myLife = ai.getLife();
final Player opponent = ai.getOpponent();
final int hlife = opponent.getLife();
final Card source = sa.getSourceCard();
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
@@ -116,7 +116,7 @@ public class LifeSetAi extends SpellAbilityAi {
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
if (source.getName().equals("Eternity Vessel")
@@ -128,18 +128,18 @@ public class LifeSetAi extends SpellAbilityAi {
// if the Target is modifying how much life is gained, this needs to
// be
// handled better
final Target tgt = sa.getTarget();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
tgt.resetTargets();
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
tgt.addTarget(opponent);
sa.getTargets().add(opponent);
} else {
if ((amount > myLife) && (myLife <= 10)) {
tgt.addTarget(ai);
sa.getTargets().add(ai);
} else if (hlife > amount) {
tgt.addTarget(opponent);
sa.getTargets().add(opponent);
} else if (amount > myLife) {
tgt.addTarget(ai);
sa.getTargets().add(ai);
} else {
return false;
}

View File

@@ -0,0 +1,35 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ManaEffectAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (ai.getGame().getPhaseHandler().is(PhaseType.MAIN2) && ComputerUtil.activateForCost(sa, ai)) {
return true;
}
return false;
}
/**
* @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(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

View File

@@ -0,0 +1,155 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class MillAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final Cost abCost = sa.getPayCosts();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if (!targetAI(ai, sa, false)) {
return false;
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
// Don't use draw abilities before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")) && source.getSVar("X").startsWith("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard =
Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent().getCardsIn(ZoneType.Library).size());
source.setSVar("PayX", Integer.toString(cardsToDiscard));
if (cardsToDiscard <= 0) {
return false;
}
}
return true;
}
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getOpponent();
if (tgt != null) {
sa.resetTargets();
if (!sa.canTarget(opp)) {
if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
return true;
}
return false;
}
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
final List<Card> pLibrary = opp.getCardsIn(ZoneType.Library);
if (pLibrary.isEmpty()) { // deck already empty, no need to mill
if (!mandatory) {
return false;
}
sa.getTargets().add(opp);
return true;
}
if (numCards >= pLibrary.size()) {
// Can Mill out Human's deck? Do it!
sa.getTargets().add(opp);
return true;
}
// Obscure case when you know what your top card is so you might?
// want to mill yourself here
// if (AI wants to mill self)
// sa.getTargets().add(AllZone.getComputerPlayer());
// else
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return targetAI(aiPlayer, sa, true);
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (!targetAI(aiPlayer, sa, mandatory)) {
return false;
}
final Card source = sa.getHostCard();
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, aiPlayer), aiPlayer.getOpponent()
.getCardsIn(ZoneType.Library).size());
source.setSVar("PayX", Integer.toString(cardsToDiscard));
}
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) {
return true;
}
}

View File

@@ -1,9 +1,9 @@
package forge.card.ability.ai;
package forge.ai.ability;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class MustAttackAi extends SpellAbilityAi {

View File

@@ -1,23 +1,22 @@
package forge.card.ability.ai;
import java.util.List;
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.Card;
import forge.CardLists;
import forge.CardPredicates;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat;
import forge.game.phase.CombatUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class MustBlockAi extends SpellAbilityAi {
@Override
@@ -34,8 +33,8 @@ public class MustBlockAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getSourceCard();
final Target abTgt = sa.getTarget();
final Card source = sa.getHostCard();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
// only use on creatures that can attack
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
@@ -44,7 +43,7 @@ public class MustBlockAi extends SpellAbilityAi {
Card attacker = null;
if (sa.hasParam("DefinedAttacker")) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("DefinedAttacker"), sa);
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
if (cards.isEmpty()) {
return false;
}
@@ -89,7 +88,7 @@ public class MustBlockAi extends SpellAbilityAi {
if (blocker == null) {
return false;
}
abTgt.addTarget(blocker);
sa.getTargets().add(blocker);
chance = true;
} else {
return false;

View File

@@ -0,0 +1,39 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class PeekAndRevealAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (sa instanceof AbilityStatic) {
return false;
}
// So far this only appears on Triggers, but will expand
// once things get converted from Dig + NoMove
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) {
AbilitySub subAb = sa.getSubAbility();
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
}
}

View File

@@ -1,20 +1,19 @@
package forge.card.ability.ai;
import java.util.List;
package forge.ai.ability;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.Card;
import forge.CardPredicates;
import forge.card.ability.SpellAbilityAi;
import forge.card.spellability.SpellAbility;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ai.ComputerUtil;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
/**
* AbilityFactory for Creature Spells.
*
@@ -46,7 +45,7 @@ public class PermanentCreatureAi extends SpellAbilityAi {
for(Card c : cards) {
ArrayList<StaticAbility> statics = c.getStaticAbilities();
for(StaticAbility s : statics) {
final Map<String, String> stabMap = s.getMapParams();
final Map<String, String> stabMap = s.parseParams();
if (!stabMap.get("Mode").equals("Continuous")) {
continue;

Some files were not shown because too many files have changed in this diff Show More